Ruxmon meetup
Ruxmon Meetup
Went to the Melbourne Ruxmon meetup 1. Glitter nail polish vs the Evil Maid, the Story 2. I put CVE in your CVE
@hoodiePony gave an intersting talk on cirumventing tamper detection
also has taken part in CH Con New Zealand hacker con
-
https://www.youtube.com/c/chconnz - looks like a treasure trove of videos
-
https://www.linkedin.com/company/chcon-nz/?originalSubdomain=nz
On the nail polish presentation
- https://www.vice.com/en/article/itll-take-more-than-glitter-nail-polish-to-stop-snooping/
- https://fahrplan.events.ccc.de/congress/2013/Fahrplan/events/5600.html
- https://boingboing.net/2013/12/31/glitter-nail-polish-is-the-bes-2.html
- https://www.wired.com/2013/12/better-data-security-nail-polish/
- https://mullvad.net/en/blog/how-tamper-protect-laptop-nail-polish
- https://www.anarsec.guide/posts/tamper/
- Detect Physical Computer Tampering - 🥷 RTP Tech Tips 📡

similar style of presentation although virtual:
DEF CON 31 DCGVR Village - hoodiepony - Ghost On The Wire -
DEFCONConference

On learning from CVE’s
@Snyff (Pentesterlab.com) did an interesting talk on looking at finding new CVE’s inside of existing CVE’s. The general premise being that people are publishing CVE’s and fixes and often the fixes are not that good and the code around them is likely to have more security vulnerabilities. This could lead to having CVE’s that are attributed to you!
The talk covered things like
CVE-2023-28109
Authorization Bypass Through User-Controlled Key play-with-docker
https://github.com/play-with-docker/play-with-docker/commit/ed82247c9ab7990ad76ec2bf1498c2b2830b6f1a
diff a/handlers/bootstrap.go b/handlers/bootstrap.go
--- a/handlers/bootstrap.go
+++ b/handlers/bootstrap.go
@@ -69,10 +70,10 @@
func Register(extend HandlerExtender) {
corsHandler := gh.CORS(gh.AllowCredentials(), gh.AllowedHeaders([]string{"x-requested-with", "content-type"}), gh.AllowedMethods([]string{"GET", "POST", "HEAD", "DELETE"}), gh.AllowedOriginValidator(func(origin string) bool {
if strings.Contains(origin, "localhost") ||
- strings.HasSuffix(origin, "play-with-docker.com") ||
- strings.HasSuffix(origin, "play-with-kubernetes.com") ||
- strings.HasSuffix(origin, "docker.com") ||
- strings.HasSuffix(origin, "play-with-go.dev") {
+ strings.HasSuffix(origin, ".play-with-docker.com") ||
+ strings.HasSuffix(origin, ".play-with-kubernetes.com") ||
+ strings.HasSuffix(origin, ".docker.com") ||
+ strings.HasSuffix(origin, ".play-with-go.dev") {
return true
}
return false
But a better fix would have been to allow the bare/apex domains to match as well. Better still treat them as a Path/URL and not a string, where you could do a proper root-domain match.
+ origin == "play-with-docker.com" ||
+ strings.HasSuffix(origin, ".play-with-docker.com") ||
+ origin == "play-with-kubernetes.com" ||
+ strings.HasSuffix(origin, ".play-with-kubernetes.com") ||
+ origin == "docker.com" ||
+ strings.HasSuffix(origin, ".docker.com") ||
+ origin == "play-with-go.dev" ||
+ strings.HasSuffix(origin, ".play-with-go.dev") {
Looking around the change, another issue is found, the match on
Contains(origin, "localhost")
opens a bunch of issues around ANY url with the term localhost
func Register(extend HandlerExtender) {
corsHandler := gh.CORS(gh.AllowCredentials(), gh.AllowedHeaders([]string{"x-requested-with", "content-type"}), gh.AllowedMethods([]string{"GET", "POST", "HEAD", "DELETE"}), gh.AllowedOriginValidator(func(origin string) bool {
// ⚠️️ ⚠️ ⚠️️️️️ what about http://localhostpentesterlab.com ???
if strings.Contains(origin, "localhost") ||
strings.HasSuffix(origin, ".play-with-docker.com") ||
strings.HasSuffix(origin, ".play-with-kubernetes.com") ||
strings.HasSuffix(origin, ".docker.com") ||
strings.HasSuffix(origin, ".play-with-go.dev") {
return true
}
return false
The learnings:
- Look around the change
- string matching is hard
CVE-2024-31860
Apache Zeppelin Path Traversal vulnerability
--- zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
+++ zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
import static org.apache.zeppelin.scheduler.Job.Status.ABORT;
import java.io.IOException;
+ import java.net.URLDecoder;
+ import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
}
notePath = notePath.replace("\r", " ").replace("\n", " ");
+
+ notePath = URLDecoder.decode(notePath, StandardCharsets.UTF_8.toString());
+ if (notePath.endsWith("/")) {
+ throw new IOException("Note name shouldn't end with '/'");
+ }
+
int pos = notePath.lastIndexOf("/");
if ((notePath.length() - pos) > 255) {
throw new IOException("Note name must be less than 255");
The problem here being that this will prevent directory traversal using %2e%2e
by decoding the value. They only decode the value once so if we recursively
encode the value with say %252e%252e we get past the fix.
# single encoding
echo "%2e%2e/%2e%2e/etc/passwd" | \
ruby -r cgi -e 'puts CGI.unescape($stdin.read)'
../../etc/passwd
# double encoding
echo "%252e%252e/%252e%252e/etc/passwd" | \
ruby -r cgi -e 'puts CGI.unescape($stdin.read)'
%2e%2e/%2e%2e/etc/passwd
# tripple encoding
echo "%25252e%25252e/%25252e%25252e/etc/passwd" | \
ruby -r cgi -e 'puts CGI.unescape($stdin.read)'
%252e%252e/%252e%252e/etc/passwd
# a recurisve solution (applied to tripple encoding)
echo "%25252e%25252e/%25252e%25252e/etc/passwd" | ruby -r cgi -e '
def recursive_decode(input)
decoded = CGI.unescape(input)
while decoded != input
input = decoded
decoded = CGI.unescape(input)
end
decoded
end
puts recursive_decode($stdin.read)'
../../etc/passwd
The learnings:
- recursive filtering
GHSA-8g98-m4j9-qww5
GHSA-8g98-m4j9-qww5 Taylored webhook validation vulnerabilities
https://github.com/tailot/taylored/commit/57b7634391959dbbdb39b387ac4dc68157cd58a1#diff-67dab5650d08966d480460df1e79b263c9b69a24ebffd30179374a9f6bbc990b
+ db.get(`SELECT id, patch_id, purchase_token, status, token_used_at
+ FROM purchases WHERE patch_id = ? AND purchase_token = ? AND status = 'COMPLETED'`,
# -- SNIP --
+
+ db.run(`UPDATE purchases SET token_used_at = CURRENT_TIMESTAMP
+ WHERE id = ?`, [row.id], function(updateErr) {
This reduced the attack but did not get rid of it completely, just reduced the likely hood as there would still be a chance for an attack if timing could work. The real solution would be to have this in a transaction or a single query get AND set. This was fixed in
but NO CVE given - “emotional damage” to the creator
Lessons learned:
- Replay to race condition
CVE-2025-12762
pgAdmin4 vulnerable to Remote Code Execution (RCE) when running in server mode
PosgreSQL allows you to run command when importing data using
"\!"at the start of the line\! touch /tmp/HACKED
the fix does not match ALL KINDS OF whitespace in the regex and there is a chance of sneaking the characters between chunks, \ at the end of one chunk and ! at beginning of next chunk
https://github.com/pgadmin-org/pgadmin4/commit/1d397395f75320ca1d4ed5e9ca721c603415e836
+ # Look for start-of-line pattern: beginning or after newline,
+ # optional spaces, then backslash
+ pattern = re.compile(br'(^|\n)[ \t]*\\')
# -- SNIP --
+
+ with open(path, "rb") as f:
+ prev_tail = b""
+ while chunk := f.read(chunk_size):
Lessons Learned:
- Regular expressions are hard
- Be greedy when filtering
- People are watching (other people are watching CVE’s and creating CVE’s)
CVE-2024-21643
CVE-2025-12203
CVE-2026-25130
CVE-2026-????
This one intruigued me due to some funky issues with toLowerCase but I could not seem to find the exact code here
git clone git@github.com:github/advisory-database.git
cd advisory-database
ag -l 2026-02-27 advisories/unreviewed/2026/02/ | xargs ag domain
advisories/unreviewed/2026/02/GHSA-hcv4-2wj7-9p5g/GHSA-hcv4-2wj7-9p5g.json
9: "details": "Unitree Go2 firmware versions V1.1.7 through V1.1.9 and
V1.1.11 (EDU) do not implement DDS authentication or authorization for the
Eclipse CycloneDDS topic rt/api/programming_actuator/request handled by
actuator_manager.py. A network-adjacent, unauthenticated attacker can join DDS
domain 0 and publish a crafted message (api_id=1002) containing arbitrary
Python, which the robot writes to disk under /unitree/etc/programming/ and
binds to a physical controller keybinding. When the keybinding is pressed, the
code executes as root and the binding persists across reboots.",
advisories/unreviewed/2026/02/GHSA-j5mf-6rh3-rhgg/GHSA-j5mf-6rh3-rhgg.json
9: "details": "CleverTap Web SDK version 1.15.2 and earlier is vulnerable to
Cross-Site Scripting (XSS) via window.postMessage. The
handleCustomHtmlPreviewPostMessageEvent function in
src/util/campaignRender/nativeDisplay.js performs insufficient origin
validation using the includes() method, which can be bypassed by an attacker
using a subdomain",
advisories/unreviewed/2026/02/GHSA-jfrq-hj9f-c8qx/GHSA-jfrq-hj9f-c8qx.json
9: "details": "CleverTap Web SDK version 1.15.2 and earlier is vulnerable to
DOM-based Cross-Site Scripting (XSS) via window.postMessage in the Visual
Builder module. The origin validation in
src/modules/visualBuilder/pageBuilder.js (lines 56-60) uses the includes()
method to verify the originUrl contains \"dashboard.clevertap.com\", which can
be bypassed by an attacker using a crafted subdomain",
And even with some AI help it suggested the CleverTap Web SDK but the code is as below and seems unrelated ¯_(ツ)_/¯
--- a/lib/controllers/xxxxx.js
+++ b/lib/controllers/xxxxx.js
@@ -94,9 +94,9 @@ module.exports = function (options) {
host = host.toLowerCase();
//check that host is from one of these domains
for (var i = 0; i < allowedDomains.length; i++) {
+ const domainLower = allowedDomains[i].toLowerCase();
if (
- host.indexOf(allowedDomains[i], host.length - allowedDomains[i].length)!== -1
+ host === domainLower || host.endsWith("." + domainLower)
) {
return true;
}
The intruigue here is that toLowerCase in many languages may convert unicode
to ASCII and the same can be true for when using regular expressions
In Ruby
ruby -e '(0..0x10FFFF).each do |cp|
next if (0xD800..0xDFFF) === cp # skip surrogate pairs
c = [cp].pack("U") # convert codepoint to UTF-8 character
next if c =~ /\A[\x20-\x7e]+\z/ # skip already-ASCII characters
u = c.upcase; d = c.downcase # case-fold both directions
# emit if upcase yields ASCII
puts "U+#{"%04X"%cp} #{c} -> upcase=#{u}" if u != c && u =~ /\A[\x20-\x7e]+\z/
# emit if downcase yields ASCII
puts "U+#{"%04X"%cp} #{c} -> downcase=#{d}" if d != c && d =~ /\A[\x20-\x7e]+\z/
end'
U+00DF ß -> upcase=SS
U+0131 ı -> upcase=I
U+017F ſ -> upcase=S
U+212A K -> downcase=k
U+FB00 ff -> upcase=FF
U+FB01 fi -> upcase=FI
U+FB02 fl -> upcase=FL
U+FB03 ffi -> upcase=FFI
U+FB04 ffl -> upcase=FFL
U+FB05 ſt -> upcase=ST
U+FB06 st -> upcase=ST
and In JavaScript
node -e 'for (let cp = 0; cp <= 0x10FFFF; cp++) {
if (cp >= 0xD800 && cp <= 0xDFFF) continue; // skip surrogate pairs
const c = String.fromCodePoint(cp); // convert codepoint to UTF-16 character
if (/^[\x20-\x7e]+$/.test(c)) continue; // skip already-ASCII characters
const u = c.toUpperCase(), d = c.toLowerCase(); // case-fold both directions
// emit if toUpperCase yields ASCII
if (u !== c && /^[\x20-\x7e]+$/.test(u))
console.log(`U+${cp.toString(16).toUpperCase().padStart(4,"0")} ${c} -> toUpperCase=${u}`);
// emit if toLowerCase yields ASCII
if (d !== c && /^[\x20-\x7e]+$/.test(d))
console.log(`U+${cp.toString(16).toUpperCase().padStart(4,"0")} ${c} -> toLowerCase=${d}`);
}'
U+00DF ß -> toUpperCase=SS
U+0131 ı -> toUpperCase=I
U+017F ſ -> toUpperCase=S
U+212A K -> toLowerCase=k
U+FB00 ff -> toUpperCase=FF
U+FB01 fi -> toUpperCase=FI
U+FB02 fl -> toUpperCase=FL
U+FB03 ffi -> toUpperCase=FFI
U+FB04 ffl -> toUpperCase=FFL
U+FB05 ſt -> toUpperCase=ST
U+FB06 st -> toUpperCase=ST
clearly facebooK.com, with a Kelvin K is not facebook.com
Lessons Learned
- Matching is hard
- Even simple modifications can have security impacts.
- Don’t just look at change, look around it
Easy Bypasses
- Bad String Matching
- Case-Sensitivity and Unicode
- SSRF Fix:
- Check for Redirect Support
- Check for DNS Rebinding (Time of Check/Time of Use - TOCTOU)
- Directory Traversal Fix:
- Check for
my/path/rooagainstmy/path/root
- Check for
Thanks the presenter 🙏
- Louis Nyffenegger
- 𝕏 @snyff
- https://www.linkedin.com/in/snyff/@snyff
- louis@pentesterlab.com
- PentesterLab.com
- 𝕏 @PentesterLab
Book -> CVE Archeologist’s Field Guide: Methodology and lessons from 10 vulnerability analyses
Find vulnerabilities being reported
- https://github.com/github/advisory-database
But I found there is also