1 00:00:00,090 --> 00:00:03,730 - Today we are having the PreAuth RCE chains 2 00:00:04,620 --> 00:00:06,270 by Jeff Hoffman. 3 00:00:06,270 --> 00:00:09,060 I got a couple of announcements before we get started. 4 00:00:09,060 --> 00:00:10,740 Please, everyone continue to wear your masks 5 00:00:10,740 --> 00:00:11,730 while you're inside. 6 00:00:11,730 --> 00:00:13,860 And please don't congregate around the walls 7 00:00:13,860 --> 00:00:15,960 around the outside, just come in and find a seat. 8 00:00:15,960 --> 00:00:18,660 If you want, come up a little closer, that'd be great. 9 00:00:18,660 --> 00:00:19,493 Here's Jeff. 10 00:00:21,140 --> 00:00:24,140 (audience clapping) 11 00:00:27,210 --> 00:00:29,280 - Hi, thanks for coming to my talk. 12 00:00:29,280 --> 00:00:31,593 PreAuth RCEs on KACE SMA. 13 00:00:32,430 --> 00:00:34,920 Today, I'm gonna be going over what KACE SMA is 14 00:00:34,920 --> 00:00:37,680 as well as the interesting attack surface of the product. 15 00:00:37,680 --> 00:00:39,300 The vulnerabilities that I found, 16 00:00:39,300 --> 00:00:40,650 how I conducted my research, 17 00:00:40,650 --> 00:00:42,540 and I have a full exploit and demo to share with you 18 00:00:42,540 --> 00:00:43,690 at the end of the talk. 19 00:00:44,940 --> 00:00:46,440 So who am I? 20 00:00:46,440 --> 00:00:47,910 My name is Jeffrey Hoffman. 21 00:00:47,910 --> 00:00:49,740 I'm a security engineer at Nuro. 22 00:00:49,740 --> 00:00:52,140 I have a background in web and desktop apps, 23 00:00:52,140 --> 00:00:54,360 as well as cloud and reverse engineering. 24 00:00:54,360 --> 00:00:55,560 I love to do security research 25 00:00:55,560 --> 00:00:57,240 and exploit dev in my free time. 26 00:00:57,240 --> 00:00:59,730 And anything else that you might need to know about me, 27 00:00:59,730 --> 00:01:01,983 if I'm willing to share it, is on my website. 28 00:01:03,690 --> 00:01:05,880 So what are MDMs? 29 00:01:05,880 --> 00:01:08,310 MDM stands for mobile device management, 30 00:01:08,310 --> 00:01:10,950 and they're typically appliances used by IT 31 00:01:10,950 --> 00:01:14,433 to manage corporate devices like laptops or workstations. 32 00:01:15,390 --> 00:01:16,440 The way they usually work 33 00:01:16,440 --> 00:01:18,960 is they'll install a privilege binary, 34 00:01:18,960 --> 00:01:20,400 usually referred to as an agent 35 00:01:20,400 --> 00:01:22,560 on whatever workstation you wanna manage. 36 00:01:22,560 --> 00:01:25,800 And that agent is going to phone home to the MDM, 37 00:01:25,800 --> 00:01:27,690 pick up jobs to run, run them 38 00:01:27,690 --> 00:01:29,940 and report back the status of those jobs. 39 00:01:29,940 --> 00:01:32,400 And so the jobs can be anything. 40 00:01:32,400 --> 00:01:34,110 They need to run as root or system, 41 00:01:34,110 --> 00:01:37,080 because sometimes those jobs require those privileges. 42 00:01:37,080 --> 00:01:40,050 For example, installing a certificate authority. 43 00:01:40,050 --> 00:01:42,120 But really, they're pretty versatile, 44 00:01:42,120 --> 00:01:43,320 that's the whole point, 45 00:01:43,320 --> 00:01:45,483 is that IT can fully manage your device. 46 00:01:46,890 --> 00:01:48,540 And that's good news for attackers. 47 00:01:48,540 --> 00:01:51,150 It makes them, essentially single points of failure, 48 00:01:51,150 --> 00:01:53,130 for an organization. 49 00:01:53,130 --> 00:01:55,770 You could, if you got admin access to an MDM, 50 00:01:55,770 --> 00:01:57,540 could do something like, push out ransomware 51 00:01:57,540 --> 00:02:00,900 to all of the workstations at once. 52 00:02:00,900 --> 00:02:03,990 And more and more MDMs, these days, are on the internet. 53 00:02:03,990 --> 00:02:06,930 With the rise of COVID and remote work, 54 00:02:06,930 --> 00:02:09,030 as well as the push for people to offer 55 00:02:09,030 --> 00:02:11,040 software service offerings. 56 00:02:11,040 --> 00:02:13,080 Some of those MDMs just have to be on the internet 57 00:02:13,080 --> 00:02:14,223 out of necessity. 58 00:02:15,750 --> 00:02:17,430 So you have this great impact. 59 00:02:17,430 --> 00:02:19,710 You have them generally being available, 60 00:02:19,710 --> 00:02:22,890 if you support remote work and you have this really, 61 00:02:22,890 --> 00:02:24,810 really interesting scenario where if a company, 62 00:02:24,810 --> 00:02:27,330 dog-foods or uses their own MDM, 63 00:02:27,330 --> 00:02:30,270 you could get into their employees workstations, 64 00:02:30,270 --> 00:02:32,310 start pushing malicious back doors 65 00:02:32,310 --> 00:02:36,210 and even reach people who only host these MDMs on-prem. 66 00:02:36,210 --> 00:02:38,610 So generally, pretty interesting attack surface. 67 00:02:39,630 --> 00:02:42,810 KACE SMA or KACE Systems Management Appliance 68 00:02:42,810 --> 00:02:45,270 is an MDM developed by Quest. 69 00:02:45,270 --> 00:02:46,470 It's written in PHP. 70 00:02:46,470 --> 00:02:48,300 It's a relatively popular choice 71 00:02:48,300 --> 00:02:49,710 for hybrid work environments, 72 00:02:49,710 --> 00:02:52,200 where you have windows, Linux and Mac workstations 73 00:02:52,200 --> 00:02:54,180 all in the same organization. 74 00:02:54,180 --> 00:02:56,730 There's about 2000 of these things on the internet, 75 00:02:56,730 --> 00:03:00,240 including a SaaS offering that Quest hosts. 76 00:03:00,240 --> 00:03:03,240 And a decent chunk of these are .gov and .edu domains, 77 00:03:03,240 --> 00:03:05,520 which imply to me that you might have 78 00:03:05,520 --> 00:03:08,280 a lot of very interesting PII readily available 79 00:03:08,280 --> 00:03:10,580 if you were to compromise something like this. 80 00:03:11,880 --> 00:03:13,833 So in terms of what would be valuable, 81 00:03:14,850 --> 00:03:16,830 admin access is really the end-all be-all 82 00:03:16,830 --> 00:03:19,920 for these types of appliances. 83 00:03:19,920 --> 00:03:21,570 Every vulnerability that you find 84 00:03:21,570 --> 00:03:23,790 is more of a means to an end to get admin, 85 00:03:23,790 --> 00:03:25,170 because at the end of the day, 86 00:03:25,170 --> 00:03:28,320 you don't really wanna exploit any kind of vulnerabilities 87 00:03:28,320 --> 00:03:29,310 on the MDM itself. 88 00:03:29,310 --> 00:03:31,110 You wanna use the intended functionality 89 00:03:31,110 --> 00:03:34,770 to pivot down to multiple devices at the same time. 90 00:03:34,770 --> 00:03:36,120 And just a little caveat 91 00:03:36,120 --> 00:03:37,830 for all the vulnerabilities I'm gonna talk about. 92 00:03:37,830 --> 00:03:39,060 I didn't look at anything 93 00:03:39,060 --> 00:03:40,890 that would require user interaction. 94 00:03:40,890 --> 00:03:42,360 I felt like that was less interesting, 95 00:03:42,360 --> 00:03:44,610 and so I have no idea what it's out there 96 00:03:44,610 --> 00:03:48,483 in terms of XSS or CSRF or those types of vulns. 97 00:03:50,160 --> 00:03:52,950 So because Quest, or rather KACE SMA, 98 00:03:52,950 --> 00:03:57,060 is written in PHP, getting a initial unrestricted shell 99 00:03:57,060 --> 00:03:59,130 was my top priority. 100 00:03:59,130 --> 00:04:01,650 That was gonna let me pull all the PHP code off the box, 101 00:04:01,650 --> 00:04:03,150 and then I could start source review 102 00:04:03,150 --> 00:04:05,310 and find more in-depth vulnerabilities. 103 00:04:05,310 --> 00:04:06,660 And so I'm gonna show you three tricks today 104 00:04:06,660 --> 00:04:08,700 that have generally been fruitful for me 105 00:04:08,700 --> 00:04:11,340 when attacking appliances, your mileage may vary, 106 00:04:11,340 --> 00:04:13,650 but I always try these three things. 107 00:04:13,650 --> 00:04:16,230 So the very first thing is just trying to break out 108 00:04:16,230 --> 00:04:19,020 of any restricted shells that might have been implemented. 109 00:04:19,020 --> 00:04:22,320 And so I just installed KACE, I read through the docs 110 00:04:22,320 --> 00:04:26,457 and there were two default users, konfig and netdiag. 111 00:04:26,457 --> 00:04:28,681 And so, they both had restricted shells. 112 00:04:28,681 --> 00:04:30,720 Konfig's was more of a text editor 113 00:04:30,720 --> 00:04:32,100 to change network settings, 114 00:04:32,100 --> 00:04:33,810 and it didn't have an immediately obvious way 115 00:04:33,810 --> 00:04:37,290 to run binaries, so I moved past that pretty quickly. 116 00:04:37,290 --> 00:04:40,710 And netdiag did have a more typical restricted shell. 117 00:04:40,710 --> 00:04:43,650 So I gathered all of the binaries that you could run, 118 00:04:43,650 --> 00:04:45,210 ran them through gtfobins, 119 00:04:45,210 --> 00:04:48,360 which is a website that collects shell breakouts. 120 00:04:48,360 --> 00:04:49,770 And the only thing that I found was 121 00:04:49,770 --> 00:04:52,380 this arp-f partial file read, 122 00:04:52,380 --> 00:04:54,000 which wasn't super useful for my case, 123 00:04:54,000 --> 00:04:57,783 because I wanted to read full PHP files, not partials. 124 00:04:59,400 --> 00:05:02,100 So that first trick didn't work. 125 00:05:02,100 --> 00:05:04,950 This second one is gonna sound a little tongue in cheek, 126 00:05:04,950 --> 00:05:09,000 but a way that I always try to get my unrestricted shell 127 00:05:09,000 --> 00:05:11,010 is to look for PostAuth RCE. 128 00:05:11,010 --> 00:05:14,880 And the nuance here is where that PostAuth RCE 129 00:05:14,880 --> 00:05:16,530 can usually be found. 130 00:05:16,530 --> 00:05:19,410 Typically appliances, not just MDMs, 131 00:05:19,410 --> 00:05:22,320 want to have some sort of backup and restore process. 132 00:05:22,320 --> 00:05:24,960 That way you can support migrating to a new appliance, 133 00:05:24,960 --> 00:05:27,720 if you need to, or just generally backing up 134 00:05:27,720 --> 00:05:29,420 what IT has already set up, right? 135 00:05:30,540 --> 00:05:32,430 But the issue is a lot of times, 136 00:05:32,430 --> 00:05:34,950 those backups and restores are implemented as 137 00:05:34,950 --> 00:05:36,690 holistic backup and restores. 138 00:05:36,690 --> 00:05:39,210 And so if they include anything important in them, 139 00:05:39,210 --> 00:05:43,290 like maybe a binary or a very important konfig, 140 00:05:43,290 --> 00:05:45,000 if there's no integrity check on those backups, 141 00:05:45,000 --> 00:05:46,290 you're gonna be able to overwrite it. 142 00:05:46,290 --> 00:05:48,960 And from there it's context dependent usually, 143 00:05:48,960 --> 00:05:51,810 but the trick is just looking for important things 144 00:05:51,810 --> 00:05:52,860 in backups. 145 00:05:52,860 --> 00:05:55,290 And so in the case of KACE, 146 00:05:55,290 --> 00:05:58,290 there was the web root actually included in the backup. 147 00:05:58,290 --> 00:06:00,570 And so my first unrestricted shell 148 00:06:00,570 --> 00:06:03,360 came from just planning a web shell and a backup 149 00:06:03,360 --> 00:06:04,810 and restoring it immediately. 150 00:06:06,060 --> 00:06:10,937 So I got excess as www, which was definitely great. 151 00:06:10,937 --> 00:06:13,020 It was a good start. 152 00:06:13,020 --> 00:06:15,360 This is, not to beat up on Quest, 153 00:06:15,360 --> 00:06:17,460 this is just a really hard problem to solve. 154 00:06:17,460 --> 00:06:21,420 You have the desire to make your backups 155 00:06:21,420 --> 00:06:23,400 as holistic as possible, 156 00:06:23,400 --> 00:06:25,800 it's generally hard to sanitize those backups, 157 00:06:25,800 --> 00:06:28,590 the integrity check is probably the way to go. 158 00:06:28,590 --> 00:06:30,990 But it just, 159 00:06:30,990 --> 00:06:32,880 generally is gonna be a fruitful attack surface 160 00:06:32,880 --> 00:06:35,010 for those reasons, combined with the fact that 161 00:06:35,010 --> 00:06:37,080 you almost always need admin on the appliance 162 00:06:37,080 --> 00:06:38,970 to trigger this functionality anyway. 163 00:06:38,970 --> 00:06:41,340 And the separation of privileges between 164 00:06:41,340 --> 00:06:46,200 being an admin on an MDM and being able to get RCE on it is 165 00:06:46,200 --> 00:06:49,353 almost nonexistent, the admin is the more important feature. 166 00:06:51,060 --> 00:06:54,540 And so RCE as www is definitely a great start, 167 00:06:54,540 --> 00:06:55,920 but ultimately you're gonna want root 168 00:06:55,920 --> 00:06:58,454 to poke around a little more invasively. 169 00:06:58,454 --> 00:06:59,400 And so from here, 170 00:06:59,400 --> 00:07:02,250 I could have either looked for a privilege escalation, or... 171 00:07:02,250 --> 00:07:06,150 And this is what I actually did, is try my third trick. 172 00:07:06,150 --> 00:07:08,460 And this third trick is essentially the same 173 00:07:08,460 --> 00:07:09,960 as the backup and restore process, 174 00:07:09,960 --> 00:07:12,360 but you don't go in through the application. 175 00:07:12,360 --> 00:07:14,730 It revolves around the fact that if you own the appliance, 176 00:07:14,730 --> 00:07:16,470 you own the virtual machine's disc, 177 00:07:16,470 --> 00:07:18,990 you can simply unmount that virtual machine disc, 178 00:07:18,990 --> 00:07:21,420 mount it in a new virtual machine 179 00:07:21,420 --> 00:07:22,860 of the same operating system 180 00:07:22,860 --> 00:07:26,550 and start overwriting whatever you want on that file system. 181 00:07:26,550 --> 00:07:29,430 And so I targeted ndsh, which was the name of 182 00:07:29,430 --> 00:07:32,760 netdiag's custom restricted shell. 183 00:07:32,760 --> 00:07:37,500 And I compiled a setuid wrapper around bin/sh. 184 00:07:37,500 --> 00:07:41,100 Set the setuid bit on it, ch owned it as root 185 00:07:41,100 --> 00:07:43,293 and I overwrote ndsh with it. 186 00:07:44,130 --> 00:07:45,630 Once I logged in as netdiag, 187 00:07:45,630 --> 00:07:48,690 I was given a root shell, right from the get go. 188 00:07:48,690 --> 00:07:51,300 And from there I could do all the really nice to haves, 189 00:07:51,300 --> 00:07:55,050 like adding a new user that's root equivalent, enabling ssh. 190 00:07:55,050 --> 00:07:58,503 And generally, I was able to be a little bit more invasive. 191 00:08:00,450 --> 00:08:03,870 So the first thing I did was gather all of the PHP code, 192 00:08:03,870 --> 00:08:05,730 and I also took this opportunity 193 00:08:05,730 --> 00:08:08,730 to just orient myself in the application. 194 00:08:08,730 --> 00:08:10,890 The logs directory was incredibly helpful, 195 00:08:10,890 --> 00:08:13,800 just prepping for strings that I saw in source 196 00:08:13,800 --> 00:08:16,110 and using that to validate assumptions, 197 00:08:16,110 --> 00:08:18,120 but just with root level access, 198 00:08:18,120 --> 00:08:19,170 there were no files that could hide, 199 00:08:19,170 --> 00:08:20,730 I could read everything. 200 00:08:20,730 --> 00:08:22,950 So I just spent some time trying to figure out 201 00:08:22,950 --> 00:08:24,903 how the appliance actually worked. 202 00:08:26,490 --> 00:08:29,700 In terms of attack surface, only 80 and 443. 203 00:08:29,700 --> 00:08:32,790 So the web UI were open to the world by default, 204 00:08:32,790 --> 00:08:34,020 there was this high end port 205 00:08:34,020 --> 00:08:35,940 that was exposed during the update process, 206 00:08:35,940 --> 00:08:39,360 but it was just a going binary that served a update log, 207 00:08:39,360 --> 00:08:42,240 so not particularly interesting. 208 00:08:42,240 --> 00:08:45,000 And so, because only the web UI was exposed, 209 00:08:45,000 --> 00:08:47,790 I could gather all of the unauthenticated attack surface 210 00:08:47,790 --> 00:08:51,720 by making a word list and just using gobuster 211 00:08:51,720 --> 00:08:53,460 to brute force over all of the files 212 00:08:53,460 --> 00:08:55,380 that were accessible in that webroot. 213 00:08:55,380 --> 00:08:57,450 You filter out the 403 forbiddens 214 00:08:57,450 --> 00:09:01,440 and you have a pretty good list of where to start. 215 00:09:01,440 --> 00:09:03,300 And that approach is great, 216 00:09:03,300 --> 00:09:06,720 it was definitely a great start, but it's a little naive 217 00:09:06,720 --> 00:09:09,180 and you can do better by 218 00:09:09,180 --> 00:09:11,580 looking for the actual requirement statements 219 00:09:11,580 --> 00:09:13,230 of the authentication checks. 220 00:09:13,230 --> 00:09:16,440 And so in this example at PHP, 221 00:09:16,440 --> 00:09:19,290 the way it imports code is through the keyword, require. 222 00:09:19,290 --> 00:09:22,680 And you can imagine auth.inc includes all the off checks, 223 00:09:22,680 --> 00:09:25,590 but generally you're looking for the imported file and 224 00:09:25,590 --> 00:09:28,830 you're filtering out all the files that actually include it. 225 00:09:28,830 --> 00:09:31,950 And so that will give you an actual source of truth 226 00:09:31,950 --> 00:09:33,720 rather than this gobuster method. 227 00:09:33,720 --> 00:09:35,610 The lists generally didn't differ that much, 228 00:09:35,610 --> 00:09:37,650 but this is just a little bit more reliable. 229 00:09:37,650 --> 00:09:40,260 And you should note that it's not gonna include 230 00:09:40,260 --> 00:09:41,310 recursive inclusions. 231 00:09:41,310 --> 00:09:43,980 So you can imagine auth two includes auth. 232 00:09:43,980 --> 00:09:45,210 You're gonna have to grab for 233 00:09:45,210 --> 00:09:47,793 things that don't include auth two or auth one. 234 00:09:50,160 --> 00:09:52,860 So at this point I had a PostAuth RCE 235 00:09:52,860 --> 00:09:54,390 and I started thinking about whether or not 236 00:09:54,390 --> 00:09:56,940 an authentication bypass would be enough. 237 00:09:56,940 --> 00:09:59,430 And unfortunately, I didn't feel like it would be 238 00:09:59,430 --> 00:10:01,530 at the time, my PostAuth RCE 239 00:10:01,530 --> 00:10:03,570 already required admin privileges, 240 00:10:03,570 --> 00:10:05,460 so that was already goal, 241 00:10:05,460 --> 00:10:09,210 it wasn't really an extra vulnerability that I wanted. 242 00:10:09,210 --> 00:10:12,360 And that meant that I would need a PostAuth RCE 243 00:10:12,360 --> 00:10:14,730 with less privilege requirements, 244 00:10:14,730 --> 00:10:16,620 or I would need to find a privilege escalation 245 00:10:16,620 --> 00:10:17,453 within the app. 246 00:10:18,810 --> 00:10:21,360 So I went hunting for those types of vulnerabilities 247 00:10:21,360 --> 00:10:25,260 and there were calls to extract all over the application. 248 00:10:25,260 --> 00:10:28,350 And if you're not intimately familiar with PHP, that's okay, 249 00:10:28,350 --> 00:10:32,340 a screenshot of the extract docs can be seen on screen. 250 00:10:32,340 --> 00:10:33,750 There's this big warning that says, 251 00:10:33,750 --> 00:10:37,230 do not use extract on attacker controlled data, 252 00:10:37,230 --> 00:10:39,600 and that is because extract, by design, 253 00:10:39,600 --> 00:10:41,820 overrides the symbol table in PHP. 254 00:10:41,820 --> 00:10:43,080 And so what that means 255 00:10:43,080 --> 00:10:45,960 is you can specify an attacker controlled key 256 00:10:45,960 --> 00:10:47,850 and an attacker controlled value, 257 00:10:47,850 --> 00:10:51,120 and if those are passed into a call to extract, 258 00:10:51,120 --> 00:10:53,250 a variable with the attacker controlled key name 259 00:10:53,250 --> 00:10:55,350 will be instantiated and it will be set to 260 00:10:55,350 --> 00:10:57,180 that attacker controlled value. 261 00:10:57,180 --> 00:10:59,130 And so that's generally pretty useful. 262 00:10:59,130 --> 00:11:01,110 You can define whatever variables you want, 263 00:11:01,110 --> 00:11:03,630 but the cooler thing is that you can redefine variables 264 00:11:03,630 --> 00:11:04,800 that already exist. 265 00:11:04,800 --> 00:11:07,740 And so two really great targets to go after are the globals, 266 00:11:07,740 --> 00:11:10,620 because they'll always be there and the session variable, 267 00:11:10,620 --> 00:11:12,820 because that's how PHP tracks your identity. 268 00:11:14,610 --> 00:11:18,120 So an unauthenticated call would let me set 269 00:11:18,120 --> 00:11:21,120 all of my session variables to whatever I wanted, 270 00:11:21,120 --> 00:11:23,760 essentially creating a full session from nothing. 271 00:11:23,760 --> 00:11:24,870 So that would've been 272 00:11:24,870 --> 00:11:27,120 a great one shot vulnerability to have, 273 00:11:27,120 --> 00:11:30,090 but unfortunately I couldn't find any unauthenticated uses 274 00:11:30,090 --> 00:11:30,993 of extract. 275 00:11:32,790 --> 00:11:35,970 Luckily though it was still called in about a 100+ places 276 00:11:35,970 --> 00:11:38,310 with attacker control data throughout the app. 277 00:11:38,310 --> 00:11:40,170 So tons of different exploit strategies, 278 00:11:40,170 --> 00:11:42,720 one cool one was you could bypass 2FA 279 00:11:42,720 --> 00:11:45,630 because your 2FA configuration was set within your session. 280 00:11:45,630 --> 00:11:48,540 So you could log in, hit an endpoint and say, 281 00:11:48,540 --> 00:11:50,633 you didn't have 2FA and you're good to go. 282 00:11:51,540 --> 00:11:53,550 The thing that's gonna be really useful for this talk though 283 00:11:53,550 --> 00:11:55,590 is the direct privilege escalation. 284 00:11:55,590 --> 00:11:56,730 And so in KACE, 285 00:11:56,730 --> 00:11:58,470 they defined all of their permissions 286 00:11:58,470 --> 00:12:01,440 as just integers and they map to different roles. 287 00:12:01,440 --> 00:12:03,480 And so exploiting-- 288 00:12:03,480 --> 00:12:05,670 Or privilege escalating rather, is as easy 289 00:12:05,670 --> 00:12:07,770 as going to a vulnerable endpoint. 290 00:12:07,770 --> 00:12:10,290 So I'm picking on mailthis.php 291 00:12:10,290 --> 00:12:14,610 and setting your KB permissions to whatever number you want. 292 00:12:14,610 --> 00:12:17,040 And you can just declare yourself a global sysadmin 293 00:12:17,040 --> 00:12:19,500 and you are good to go and you can 294 00:12:19,500 --> 00:12:21,500 start using all the admin functionality. 295 00:12:23,190 --> 00:12:25,650 Now I didn't pick mailthis.php at random, 296 00:12:25,650 --> 00:12:27,570 this is a pretty interesting file. 297 00:12:27,570 --> 00:12:31,050 I looked all over at these calls to extract for, 298 00:12:31,050 --> 00:12:33,420 like I said, ones without authentication checks. 299 00:12:33,420 --> 00:12:34,980 And I didn't find any of those, 300 00:12:34,980 --> 00:12:37,860 but mailthis.php had a lighter authentication check 301 00:12:37,860 --> 00:12:38,820 than normal. 302 00:12:38,820 --> 00:12:40,680 And so it's not important you understand 303 00:12:40,680 --> 00:12:42,990 what the full authentication check looked like, 304 00:12:42,990 --> 00:12:45,330 but what is important is that you understand 305 00:12:45,330 --> 00:12:47,160 checking for KB valid user session 306 00:12:47,160 --> 00:12:50,970 was one of many checks that Quest intended to do. 307 00:12:50,970 --> 00:12:54,030 So this is just an ad hoc partial authentication check. 308 00:12:54,030 --> 00:12:56,700 It essentially meant you logged in, 309 00:12:56,700 --> 00:12:58,980 not you had a fully realized session. 310 00:12:58,980 --> 00:13:01,230 And so that just lowered the bar ever so slightly 311 00:13:01,230 --> 00:13:03,540 in terms of the kind of authentication bypasses 312 00:13:03,540 --> 00:13:04,590 that would be useful. 313 00:13:06,600 --> 00:13:08,520 Now I have PostAuth RCE. 314 00:13:08,520 --> 00:13:10,440 I have a great privilege escalation, 315 00:13:10,440 --> 00:13:11,820 multiple endpoints in the app. 316 00:13:11,820 --> 00:13:15,750 So I'm gonna be able to hit admin and even trigger that RCE. 317 00:13:15,750 --> 00:13:18,630 So looking for bypasses or authentication bypasses 318 00:13:18,630 --> 00:13:19,983 is now worth the time. 319 00:13:22,620 --> 00:13:24,540 So here's the first one I found. 320 00:13:24,540 --> 00:13:27,390 Essentially, Quest appeared to use md5 321 00:13:27,390 --> 00:13:30,570 and sha1 password hashes once upon a time. 322 00:13:30,570 --> 00:13:32,670 That was no longer the case when I looked at the app, 323 00:13:32,670 --> 00:13:36,720 but they did have to leave the legacy comparisons in. 324 00:13:36,720 --> 00:13:39,060 So that users who had created their account 325 00:13:39,060 --> 00:13:41,730 when the password hashes were md5 or sha1 326 00:13:41,730 --> 00:13:42,900 could still log in. 327 00:13:42,900 --> 00:13:44,100 Essentially you'd log in. 328 00:13:44,100 --> 00:13:45,750 If one of these checks succeeded, 329 00:13:45,750 --> 00:13:49,500 they would rehash your password and it would be rotated. 330 00:13:49,500 --> 00:13:53,490 And so what that coder view told me was, 331 00:13:53,490 --> 00:13:56,640 there wasn't gonna be any md5 hashes or sha1 hashes 332 00:13:56,640 --> 00:13:59,910 readily available, but if there was one, 333 00:13:59,910 --> 00:14:01,500 say an old account that had never 334 00:14:01,500 --> 00:14:03,330 had their password rotated, 335 00:14:03,330 --> 00:14:07,050 the loose comparisons there in that code block on screen 336 00:14:07,050 --> 00:14:07,983 was significant. 337 00:14:09,150 --> 00:14:12,090 If you're not intimately familiar with PHP, that's okay. 338 00:14:12,090 --> 00:14:14,730 It's dynamically typed language and equals equals 339 00:14:14,730 --> 00:14:16,590 is the loose comparison operator, 340 00:14:16,590 --> 00:14:17,880 as opposed to other languages 341 00:14:17,880 --> 00:14:20,430 where you would normally assume it's a strict comparison. 342 00:14:20,430 --> 00:14:24,420 And so what that means is PHP is going to try to infer 343 00:14:24,420 --> 00:14:27,030 the actual type of the variable it's looking at. 344 00:14:27,030 --> 00:14:29,220 And that's typically referred to as type juggling. 345 00:14:29,220 --> 00:14:33,210 It'll try to coerce the value to a bunch of different types 346 00:14:33,210 --> 00:14:35,850 and if one of those checks succeeds, 347 00:14:35,850 --> 00:14:38,280 the entire loose comparison succeeds. 348 00:14:38,280 --> 00:14:41,130 And so there are three examples on screen right now, 349 00:14:41,130 --> 00:14:43,590 those two bullets in red return false, 350 00:14:43,590 --> 00:14:46,350 but the bullet in green returns true. 351 00:14:46,350 --> 00:14:50,250 And that is because in PHP, oh well, just generally, 352 00:14:50,250 --> 00:14:54,540 a number and then E is interpreted as scientific notation 353 00:14:54,540 --> 00:14:56,100 for exponentiation. 354 00:14:56,100 --> 00:14:59,250 And so when you have a hash or a string 355 00:14:59,250 --> 00:15:03,660 that starts with 0e and then has all numerals after it, 356 00:15:03,660 --> 00:15:05,424 during that type juggling process, 357 00:15:05,424 --> 00:15:08,610 PHP will interpret that as zero to the power of something 358 00:15:08,610 --> 00:15:11,220 and zero to the power of anything is zero. 359 00:15:11,220 --> 00:15:13,410 So if you have two strings 360 00:15:13,410 --> 00:15:16,140 that aren't actually equivalent strings, 361 00:15:16,140 --> 00:15:19,080 but are of that magic hashing format, 362 00:15:19,080 --> 00:15:21,240 when you do a loose comparison between those 363 00:15:21,240 --> 00:15:22,473 it'll evaluate to true. 364 00:15:23,825 --> 00:15:26,677 And so I said that there probably wouldn't be any sha1 365 00:15:27,527 --> 00:15:29,130 or md5 hashes in the database, 366 00:15:29,130 --> 00:15:30,660 but I needed to go and check anyway. 367 00:15:30,660 --> 00:15:32,550 So rather than recovering the root password, 368 00:15:32,550 --> 00:15:33,930 I just restarted the database 369 00:15:33,930 --> 00:15:35,310 without authentication, locally, 370 00:15:35,310 --> 00:15:36,930 and added a root equivalent user 371 00:15:36,930 --> 00:15:38,760 so that I could poke around. 372 00:15:38,760 --> 00:15:39,990 And when I started looking 373 00:15:39,990 --> 00:15:42,840 at the actual password hash table, 374 00:15:42,840 --> 00:15:44,280 it confirmed my code review. 375 00:15:44,280 --> 00:15:48,660 There were these new actually salted hashes for passwords. 376 00:15:48,660 --> 00:15:50,190 And that meant, to actually test, 377 00:15:50,190 --> 00:15:54,030 I had to add a new user and fake them having 378 00:15:54,030 --> 00:15:56,490 an old md5 or sha1 password hash. 379 00:15:56,490 --> 00:15:59,970 And so I planted this 0e333 value, 380 00:15:59,970 --> 00:16:03,030 I have no idea what could possibly hash to that value, 381 00:16:03,030 --> 00:16:05,310 but it is of the magic hash format. 382 00:16:05,310 --> 00:16:08,130 And so to test, I used a pre-computed value 383 00:16:08,130 --> 00:16:10,227 whose md5 hash is gonna-- 384 00:16:11,580 --> 00:16:14,430 The output is going to be another magic hash. 385 00:16:14,430 --> 00:16:15,630 And the hunch was that 386 00:16:15,630 --> 00:16:17,130 when those two things would be compared, 387 00:16:17,130 --> 00:16:19,380 it would evaluate to true and I could log in. 388 00:16:20,393 --> 00:16:23,550 And so in practice, after I planted that magic hash, 389 00:16:23,550 --> 00:16:25,860 I just submitted the pre-computed value 390 00:16:25,860 --> 00:16:27,510 as my login password. 391 00:16:27,510 --> 00:16:29,850 The hashing went through and you can see 392 00:16:29,850 --> 00:16:31,140 in the location redirect, 393 00:16:31,140 --> 00:16:33,180 I wasn't kicked back to the login screen, 394 00:16:33,180 --> 00:16:34,950 it actually took me to a dashboard. 395 00:16:34,950 --> 00:16:36,873 So I successfully authenticated. 396 00:16:37,710 --> 00:16:39,510 And just to confirm my code review, 397 00:16:39,510 --> 00:16:41,040 I went and looked at the table afterwards 398 00:16:41,040 --> 00:16:43,470 and the password migration did actually happen. 399 00:16:43,470 --> 00:16:46,020 So this vulnerability effectively amounts to 400 00:16:46,020 --> 00:16:47,103 a password change. 401 00:16:48,660 --> 00:16:50,400 Now this is super cool 402 00:16:50,400 --> 00:16:52,740 and definitely an authentication bypass, 403 00:16:52,740 --> 00:16:54,180 but it has a few issues. 404 00:16:54,180 --> 00:16:56,340 So first off you have to get lucky, 405 00:16:56,340 --> 00:16:58,470 there's no way to control what somebody's password hash 406 00:16:58,470 --> 00:16:59,700 is going to come out to. 407 00:16:59,700 --> 00:17:01,500 And so you are just hoping 408 00:17:01,500 --> 00:17:03,030 that whatever someone has submitted 409 00:17:03,030 --> 00:17:06,180 will somehow hash to this magic hash value. 410 00:17:06,180 --> 00:17:07,950 There was one interesting code path 411 00:17:07,950 --> 00:17:10,800 that made this attack a little bit more plausible, 412 00:17:10,800 --> 00:17:14,130 essentially KACE, they supported importing 413 00:17:14,130 --> 00:17:15,990 your entire user directory from LDAP 414 00:17:15,990 --> 00:17:19,050 and how this seemed to work in practice was 415 00:17:19,050 --> 00:17:20,550 you would point it at LDAP. 416 00:17:20,550 --> 00:17:22,080 All your users would get imported, 417 00:17:22,080 --> 00:17:24,600 but they were imported as unprivileged accounts. 418 00:17:24,600 --> 00:17:26,880 And it seemed like what you were supposed to do 419 00:17:26,880 --> 00:17:29,550 was give the accounts that actually 420 00:17:29,550 --> 00:17:33,600 were supposed to access this app privileges manually. 421 00:17:33,600 --> 00:17:35,160 But during that import, 422 00:17:35,160 --> 00:17:37,890 the password hashes were actually set to md5 values 423 00:17:37,890 --> 00:17:40,170 and you're importing your entire user directory. 424 00:17:40,170 --> 00:17:43,260 So not only do you have one md5 value, 425 00:17:43,260 --> 00:17:47,070 you have tons and tons of these unactivated accounts. 426 00:17:47,070 --> 00:17:48,690 They don't have any permissions, 427 00:17:48,690 --> 00:17:51,390 but because of that ad hoc authentication check 428 00:17:51,390 --> 00:17:53,690 and mailthis.php that doesn't actually matter. 429 00:17:54,840 --> 00:17:58,200 So when you log in as one of those deactivated accounts, 430 00:17:58,200 --> 00:17:59,490 this is what you'll actually see. 431 00:17:59,490 --> 00:18:01,320 You get kicked out to the login screen 432 00:18:01,320 --> 00:18:02,670 and it says access denied, 433 00:18:02,670 --> 00:18:06,120 you are not authorized to access any of the tabs. 434 00:18:06,120 --> 00:18:08,250 And even when you look at the request in response, 435 00:18:08,250 --> 00:18:10,440 it appears like the login fails. 436 00:18:10,440 --> 00:18:13,290 You can tell this because it's resetting your kbox ID, 437 00:18:13,290 --> 00:18:14,550 which was the session cookie. 438 00:18:14,550 --> 00:18:17,250 And so normally when an app does that, 439 00:18:17,250 --> 00:18:18,210 it's saying, hey, 440 00:18:18,210 --> 00:18:21,240 restart the authentication flow, this did not work. 441 00:18:21,240 --> 00:18:23,640 But if you look at the session on disk, 442 00:18:23,640 --> 00:18:26,010 it actually has KB valid user session set. 443 00:18:26,010 --> 00:18:30,780 And so you can resubmit that old session ID hit mailthis.php 444 00:18:30,780 --> 00:18:32,580 and privilege escalate to admin, 445 00:18:32,580 --> 00:18:36,000 just from these unactivated or unprivileged accounts 446 00:18:36,000 --> 00:18:38,100 that were never really supposed to be used. 447 00:18:38,100 --> 00:18:41,400 And so this is definitely a fully plausible attack scenario, 448 00:18:41,400 --> 00:18:43,560 but like I said, you have to get lucky, 449 00:18:43,560 --> 00:18:46,320 it requires them actually using this feature. 450 00:18:46,320 --> 00:18:48,030 And the odds aren't even that good if they are. 451 00:18:48,030 --> 00:18:50,223 So I moved on looking for something better. 452 00:18:52,110 --> 00:18:54,060 The second authentication bypass I found 453 00:18:54,060 --> 00:18:57,150 was definitely slightly better in terms of exploitability, 454 00:18:57,150 --> 00:18:59,820 but it was a non default feature. 455 00:18:59,820 --> 00:19:02,670 And so, Quest doesn't make just KACE SMA, 456 00:19:02,670 --> 00:19:04,620 they make a bunch of different appliances. 457 00:19:04,620 --> 00:19:06,870 And one thing that they wanted to support for all of these 458 00:19:06,870 --> 00:19:09,150 is this magic login. 459 00:19:09,150 --> 00:19:12,420 And so you would pair two Quest appliances together 460 00:19:12,420 --> 00:19:14,100 and they would generate tokens for each other 461 00:19:14,100 --> 00:19:16,470 that were redeemable at the other appliance. 462 00:19:16,470 --> 00:19:19,140 And so when you are in one appliance, 463 00:19:19,140 --> 00:19:21,300 you click on a link generated for you. 464 00:19:21,300 --> 00:19:23,580 You've redeemed your token and you are logged in 465 00:19:23,580 --> 00:19:26,580 as your user, but in a different appliance. 466 00:19:26,580 --> 00:19:30,510 And the token generation can be seen on screen in bold. 467 00:19:30,510 --> 00:19:33,960 It is an md5 hash, but there's no magic hashing going on. 468 00:19:33,960 --> 00:19:37,320 The real issue is that the input is essentially guessable. 469 00:19:37,320 --> 00:19:40,500 So unique ID is taking two arguments, 470 00:19:40,500 --> 00:19:43,950 the first is prefix, the second is more entropy. 471 00:19:43,950 --> 00:19:47,100 The more entropy does not actually matter for this attack, 472 00:19:47,100 --> 00:19:49,200 but the prefix is generated by micro time. 473 00:19:49,200 --> 00:19:51,540 It's just the current time at microseconds. 474 00:19:51,540 --> 00:19:56,310 It's definitely not a secure, random value to use. 475 00:19:56,310 --> 00:19:58,830 And when you look at the unique ID docs, 476 00:19:58,830 --> 00:20:01,140 you see that most of unique IDs entropy 477 00:20:01,140 --> 00:20:03,570 also comes from the current time in microseconds. 478 00:20:03,570 --> 00:20:06,450 And so you essentially have the randomness 479 00:20:06,450 --> 00:20:10,390 coming from two calls to a totally guessable value 480 00:20:11,340 --> 00:20:14,760 and the attack in practice would look like you brute forcing 481 00:20:14,760 --> 00:20:17,850 a bunch of different outputs for these two micro time calls 482 00:20:17,850 --> 00:20:22,110 and generating your tokens with the md5 hashing, 483 00:20:22,110 --> 00:20:24,090 but you would just continue to iterate 484 00:20:24,090 --> 00:20:25,920 until you guess the right token. 485 00:20:25,920 --> 00:20:28,590 And so this is definitely more exploitable and in practice, 486 00:20:28,590 --> 00:20:31,200 there's no luck involved, but it's non default. 487 00:20:31,200 --> 00:20:33,510 No one's forcing anyone to buy two Quest appliances 488 00:20:33,510 --> 00:20:35,760 and even if you have tons of quest appliances, 489 00:20:35,760 --> 00:20:37,290 no one's forcing you to link them 490 00:20:37,290 --> 00:20:40,040 and actually use this functionality in the first place. 491 00:20:41,370 --> 00:20:44,100 So those are two of the three chains that I wanted to cover. 492 00:20:44,100 --> 00:20:45,480 They're not so great. 493 00:20:45,480 --> 00:20:47,953 They're really not reliably exploitable in practice, 494 00:20:47,953 --> 00:20:49,983 but the third one I'm about to cover is. 495 00:20:51,330 --> 00:20:53,820 So those were the only authentication bypasses 496 00:20:53,820 --> 00:20:56,460 that auditing the user attack surface turned up. 497 00:20:56,460 --> 00:20:58,170 And after I was done with that, 498 00:20:58,170 --> 00:21:00,180 I turned to the agent communication. 499 00:21:00,180 --> 00:21:01,620 My hope was maybe authentication 500 00:21:01,620 --> 00:21:03,270 was implemented a little bit differently, 501 00:21:03,270 --> 00:21:07,050 and I could find some more authentication bypasses there. 502 00:21:07,050 --> 00:21:08,730 And I actually landed in this file, 503 00:21:08,730 --> 00:21:11,130 download agent installer.php, 504 00:21:11,130 --> 00:21:12,660 and there was clear command injection, 505 00:21:12,660 --> 00:21:16,650 but it was behind this ad hoc authentication check 506 00:21:16,650 --> 00:21:19,830 where you had to submit some secret parameter called, serv. 507 00:21:19,830 --> 00:21:22,200 And so the command injection should be clear. 508 00:21:22,200 --> 00:21:23,880 Org ID is a attacker controlled, 509 00:21:23,880 --> 00:21:26,850 it's obtaining temp location, which contains zip name 510 00:21:26,850 --> 00:21:27,990 and then both of those variables 511 00:21:27,990 --> 00:21:29,523 are used in the call to exec. 512 00:21:31,800 --> 00:21:34,260 So I started digging into how serv was created, 513 00:21:34,260 --> 00:21:35,580 it was the only thing stopping me 514 00:21:35,580 --> 00:21:37,560 from triggering the command injection. 515 00:21:37,560 --> 00:21:40,440 And as it turns out serv is the hashed serial number 516 00:21:40,440 --> 00:21:41,970 of the appliance. 517 00:21:41,970 --> 00:21:43,170 It's essentially meant to be 518 00:21:43,170 --> 00:21:45,630 a boot strapping secret for agents that 519 00:21:45,630 --> 00:21:47,700 I guess, just wasn't unique to every agent, 520 00:21:47,700 --> 00:21:49,110 but that's a little bit easier 521 00:21:49,110 --> 00:21:51,150 than making a unique username and password 522 00:21:51,150 --> 00:21:53,220 or doing something else. 523 00:21:53,220 --> 00:21:55,560 So it was like that for convenience, 524 00:21:55,560 --> 00:21:57,690 but it was also good news for me. 525 00:21:57,690 --> 00:22:01,590 The serv parameter was the sha256 hash of the serial number. 526 00:22:01,590 --> 00:22:05,220 And so an unauthenticated LFI would work 527 00:22:05,220 --> 00:22:06,540 because the actual function 528 00:22:06,540 --> 00:22:09,960 that retrieved the serial number, just read it from disk. 529 00:22:09,960 --> 00:22:12,090 So if I could retrieve the serial number like that 530 00:22:12,090 --> 00:22:15,570 with my LFI, I would be able to calculate serv myself. 531 00:22:15,570 --> 00:22:18,660 And I would be able to exploit the command injection. 532 00:22:18,660 --> 00:22:21,810 Unfortunately, depending on the installation type, 533 00:22:21,810 --> 00:22:23,940 there was one, the serial number 534 00:22:23,940 --> 00:22:25,710 would be stored in one of three places. 535 00:22:25,710 --> 00:22:28,020 And one of those file names had the MAC address 536 00:22:28,020 --> 00:22:29,430 of the device appended to it. 537 00:22:29,430 --> 00:22:31,620 So I would need a way to leak the MAC address 538 00:22:31,620 --> 00:22:33,840 of the device before I could actually exploit this 539 00:22:33,840 --> 00:22:34,773 in the worst KACE. 540 00:22:35,820 --> 00:22:37,770 Luckily for me, when you log in, 541 00:22:37,770 --> 00:22:39,780 the MAC address is presented to you 542 00:22:39,780 --> 00:22:42,510 and that contents of the message 543 00:22:42,510 --> 00:22:45,360 is actually stored in etc issue, which is world readable. 544 00:22:45,360 --> 00:22:48,930 And so if you had an LFI, you could read etc issue, 545 00:22:48,930 --> 00:22:50,040 pull the MAC address, 546 00:22:50,040 --> 00:22:52,680 then you could read that final name and the worst KACE, 547 00:22:52,680 --> 00:22:54,990 pull the serial number and calculate serv 548 00:22:54,990 --> 00:22:56,373 to bypass authentication. 549 00:22:57,990 --> 00:22:59,730 Now, knowing that I went around 550 00:22:59,730 --> 00:23:03,090 and audited high and low for unauthenticated LFIs 551 00:23:03,090 --> 00:23:04,950 and I came up empty handed. 552 00:23:04,950 --> 00:23:06,240 So I took a step back 553 00:23:06,240 --> 00:23:08,190 and started thinking about what I could use 554 00:23:08,190 --> 00:23:10,080 as an LFI equivalent. 555 00:23:10,080 --> 00:23:14,160 And as it turns out, the SQL queries made by the app 556 00:23:14,160 --> 00:23:16,200 were being made as the root user, 557 00:23:16,200 --> 00:23:18,930 which meant that if I could find SQL injection, 558 00:23:18,930 --> 00:23:21,420 I would be able to use directives like load file, 559 00:23:21,420 --> 00:23:24,720 to read contents from disk. 560 00:23:24,720 --> 00:23:27,330 And depending on the SQL injection quality, 561 00:23:27,330 --> 00:23:30,280 I would be able to use that in the same way I'd use an LFI. 562 00:23:32,160 --> 00:23:34,140 So a single LFI was all that was needed. 563 00:23:34,140 --> 00:23:36,840 And this code block on screen is actually 564 00:23:36,840 --> 00:23:39,090 in the same file as the command injection, 565 00:23:39,090 --> 00:23:40,590 but it comes before serv-- 566 00:23:40,590 --> 00:23:42,180 Or the check for serv rather. 567 00:23:42,180 --> 00:23:44,190 So technically it's not authenticated. 568 00:23:44,190 --> 00:23:45,390 And it's not important that you remember 569 00:23:45,390 --> 00:23:46,560 exactly what's going on here. 570 00:23:46,560 --> 00:23:48,150 What is important is that you remember, 571 00:23:48,150 --> 00:23:49,770 validate org comes first 572 00:23:49,770 --> 00:23:51,930 and validate agent version comes second. 573 00:23:51,930 --> 00:23:54,660 If either of those queries fails, the app 404s 574 00:23:54,660 --> 00:23:56,373 and it'll exit immediately. 575 00:23:58,320 --> 00:24:01,920 So validate agent version has clear SQL injection. 576 00:24:01,920 --> 00:24:04,080 You can see org ID is just directly placed 577 00:24:04,080 --> 00:24:06,120 in the SQL query string, 578 00:24:06,120 --> 00:24:08,550 but validate org is going to come first. 579 00:24:08,550 --> 00:24:12,090 And unfortunately, org ID is properly parameterized there. 580 00:24:12,090 --> 00:24:14,550 So when I was doing code review, 581 00:24:14,550 --> 00:24:16,860 this looked unexploitable, org IDs, 582 00:24:16,860 --> 00:24:19,350 the ones stored in the database are stored as integers. 583 00:24:19,350 --> 00:24:23,070 And so I can't make an SQL injection payload 584 00:24:23,070 --> 00:24:23,973 out of integers. 585 00:24:25,260 --> 00:24:26,250 Luckily for me though, 586 00:24:26,250 --> 00:24:28,680 I was just spraying around some testing payloads 587 00:24:28,680 --> 00:24:31,200 to actually locate the relevant logs. 588 00:24:31,200 --> 00:24:34,530 And I had accidentally hit the SQL injection 589 00:24:34,530 --> 00:24:36,420 invalidate agent version. 590 00:24:36,420 --> 00:24:38,970 And I didn't really understand how that was even possible. 591 00:24:38,970 --> 00:24:42,150 So I had to work backwards from this, 592 00:24:42,150 --> 00:24:46,740 but what had happened was MariaDB actually type juggles. 593 00:24:46,740 --> 00:24:50,310 And so this is in their docs, it's some convenience feature 594 00:24:50,310 --> 00:24:52,770 for strings with differing units. 595 00:24:52,770 --> 00:24:55,560 An example use case of this feature is on screen. 596 00:24:55,560 --> 00:25:00,360 It's inferred that the developer writing this query 597 00:25:00,360 --> 00:25:02,700 really does wanna operate on these two values 598 00:25:02,700 --> 00:25:03,690 with differing units. 599 00:25:03,690 --> 00:25:07,560 And so if it sees a string that starts with numerals 600 00:25:07,560 --> 00:25:09,630 and then contains non numeric text, 601 00:25:09,630 --> 00:25:13,620 it interprets that as units and it will just drop them. 602 00:25:13,620 --> 00:25:18,620 And so the validate org query looks exactly like this. 603 00:25:18,690 --> 00:25:22,200 If you just append the SQL injection payload, 604 00:25:22,200 --> 00:25:25,770 it will get dropped and MariaDB happily 605 00:25:25,770 --> 00:25:27,090 treats that as just a one 606 00:25:27,090 --> 00:25:31,203 and the query actually returns successfully so, pretty cool. 607 00:25:32,820 --> 00:25:35,520 So that means that we can actually hit the SQLi 608 00:25:35,520 --> 00:25:38,310 in validate agent version, but there are still a few issues 609 00:25:38,310 --> 00:25:39,750 that we have to work around. 610 00:25:39,750 --> 00:25:43,440 So first off SQL responses aren't visible. 611 00:25:43,440 --> 00:25:46,020 Secondly, basename is called on org ID, 612 00:25:46,020 --> 00:25:49,200 which means that we can't directly pass it in paths 613 00:25:49,200 --> 00:25:51,690 of the files we wanna read like etc issue, 614 00:25:51,690 --> 00:25:53,820 because the payload will be truncated. 615 00:25:53,820 --> 00:25:57,600 And finally org ID has to actually be valid SQL 616 00:25:57,600 --> 00:25:59,460 and command injection because 617 00:25:59,460 --> 00:26:01,470 we need validate agent version to succeed 618 00:26:01,470 --> 00:26:03,820 when we actually trigger the command injection. 619 00:26:05,280 --> 00:26:06,867 So to start working around that, 620 00:26:06,867 --> 00:26:10,170 the first thing I did was create a blind SQL oracle, 621 00:26:10,170 --> 00:26:12,870 and I essentially abused the behavior 622 00:26:12,870 --> 00:26:14,730 of that immediate 404. 623 00:26:14,730 --> 00:26:17,910 So when queries fail, the app 404s. 624 00:26:17,910 --> 00:26:21,360 When queries succeed, the app eventually returns a 200. 625 00:26:21,360 --> 00:26:24,390 And so just by looking at the HTTP response codes, 626 00:26:24,390 --> 00:26:27,060 you could infer the outcome of the comparison 627 00:26:27,060 --> 00:26:28,233 in the SQL injection. 628 00:26:29,490 --> 00:26:30,973 Now that's definitely useful, 629 00:26:30,973 --> 00:26:34,890 but we still have to get around that second constraint 630 00:26:34,890 --> 00:26:36,120 of base name being called. 631 00:26:36,120 --> 00:26:37,440 And so the way that I did that 632 00:26:37,440 --> 00:26:40,233 was just using the concat directive. 633 00:26:41,340 --> 00:26:43,590 And so all of this is nested within a call to load file, 634 00:26:43,590 --> 00:26:47,280 but xerox 2F is the ascii character 635 00:26:47,280 --> 00:26:51,030 or the integer value for ascii/ 636 00:26:51,030 --> 00:26:53,850 and so that output of the concat call 637 00:26:53,850 --> 00:26:56,040 will result in etc issue, 638 00:26:56,040 --> 00:26:58,490 which then could be passed directly to load file. 639 00:27:00,270 --> 00:27:01,890 Now, I still need to pull out single bytes 640 00:27:01,890 --> 00:27:03,330 to make my oracle useful. 641 00:27:03,330 --> 00:27:05,070 And so the way that I chose to do that 642 00:27:05,070 --> 00:27:08,070 was dynamically rather than hard coded offsets. 643 00:27:08,070 --> 00:27:10,590 And so I used locate with load file 644 00:27:10,590 --> 00:27:12,540 in order to find the starting address 645 00:27:12,540 --> 00:27:14,708 of the actual things I wanted to bruteforce. 646 00:27:14,708 --> 00:27:17,550 And then I used substring to pull out a singular bite, 647 00:27:17,550 --> 00:27:20,040 and then I could just bruteforce the MAC address 648 00:27:20,040 --> 00:27:22,110 and serial number because they were alphanumerics. 649 00:27:22,110 --> 00:27:23,973 So, relatively small search space. 650 00:27:25,800 --> 00:27:28,830 And the full strategy as I just described, it will work. 651 00:27:28,830 --> 00:27:31,440 You can use the SQLi to read your two local files, 652 00:27:31,440 --> 00:27:32,940 calculate serv and then 653 00:27:32,940 --> 00:27:34,770 you can trigger your command injection. 654 00:27:34,770 --> 00:27:36,450 We just have that last pesky issue 655 00:27:36,450 --> 00:27:38,420 of needing a payload that is valid SQL 656 00:27:38,420 --> 00:27:41,160 and valid command injection. 657 00:27:41,160 --> 00:27:42,660 And so the way that I got around that, 658 00:27:42,660 --> 00:27:45,150 the payload can be seen on screen. 659 00:27:45,150 --> 00:27:48,090 Essentially, the semicolon delimits SQL. 660 00:27:48,090 --> 00:27:51,600 I used a hyphen hyphen, which is a comment in MariaDB. 661 00:27:51,600 --> 00:27:52,710 You can't use the pound sign 662 00:27:52,710 --> 00:27:54,900 because it'd comment out your command injection, 663 00:27:54,900 --> 00:27:57,480 and then the dollar sign and parentheses 664 00:27:57,480 --> 00:27:59,010 should be pretty recognizable 665 00:27:59,010 --> 00:28:01,230 and to get around base name for payloads, 666 00:28:01,230 --> 00:28:02,943 I just wrapped it all in base64. 667 00:28:03,900 --> 00:28:05,010 And so from here, 668 00:28:05,010 --> 00:28:08,640 I could trigger my RCE unauthenticated as www, 669 00:28:08,640 --> 00:28:10,290 but I really wanted root 670 00:28:10,290 --> 00:28:12,480 just 'cause I thought it would be more fun. 671 00:28:12,480 --> 00:28:16,020 And so I ripped this methodology straight from the OSCP, 672 00:28:16,020 --> 00:28:18,000 I just looked for custom things running as root. 673 00:28:18,000 --> 00:28:19,710 And there were a few PHP files 674 00:28:19,710 --> 00:28:21,150 that I immediately started looking at 675 00:28:21,150 --> 00:28:23,550 because they were gonna be really easy to audit. 676 00:28:24,780 --> 00:28:26,970 And so one of the things that I looked at 677 00:28:26,970 --> 00:28:28,890 was called kbserver. 678 00:28:28,890 --> 00:28:30,120 The long and short of it was, 679 00:28:30,120 --> 00:28:32,220 it listened on a world rideable Unix socket, 680 00:28:32,220 --> 00:28:34,260 and it was listening for commands 681 00:28:34,260 --> 00:28:36,990 in the form of command, argument and argument. 682 00:28:36,990 --> 00:28:38,820 And so there were a bunch of different handlers 683 00:28:38,820 --> 00:28:40,920 or commands that you could run. 684 00:28:40,920 --> 00:28:43,380 One of the interesting ones is shown on screen now, 685 00:28:43,380 --> 00:28:44,700 and it does essentially 686 00:28:44,700 --> 00:28:46,500 what the name of the command applies. 687 00:28:46,500 --> 00:28:48,810 It takes an attacker controlled source, 688 00:28:48,810 --> 00:28:51,030 moves it to an attacker controlled destination 689 00:28:51,030 --> 00:28:54,567 and then for good measure CHown's it is www. 690 00:28:54,567 --> 00:28:56,400 And so that is relatively straightforward 691 00:28:56,400 --> 00:28:57,780 logical privilege escalation, 692 00:28:57,780 --> 00:28:59,550 you could override an important binary 693 00:28:59,550 --> 00:29:01,230 or some important file, 694 00:29:01,230 --> 00:29:03,210 but I chose to abuse the command injection 695 00:29:03,210 --> 00:29:05,860 that was in the actual implementation of the handler. 696 00:29:07,530 --> 00:29:09,810 So here's the current state, 697 00:29:09,810 --> 00:29:12,630 the two chains on the right and left aren't really reliable, 698 00:29:12,630 --> 00:29:14,430 or so great, but the middle one is, 699 00:29:14,430 --> 00:29:16,920 and you can execute commands as fruit 700 00:29:16,920 --> 00:29:18,723 from an unauthenticated perspective. 701 00:29:19,950 --> 00:29:21,800 And so now I have a demo to show you. 702 00:29:25,950 --> 00:29:27,120 Oh great, it's already up. 703 00:29:27,120 --> 00:29:29,620 (Jeff laughs) 704 00:29:32,490 --> 00:29:34,350 All right, so the first thing it does-- 705 00:29:34,350 --> 00:29:37,090 So the first thing it does is check 706 00:29:38,250 --> 00:29:40,530 that it can trigger the SQL injection. 707 00:29:40,530 --> 00:29:43,020 So it knows if the appliance is vulnerable or not. 708 00:29:43,020 --> 00:29:47,010 And right now it's just using the techniques with the SQLi 709 00:29:47,010 --> 00:29:48,660 to leak the MAC address. 710 00:29:48,660 --> 00:29:51,030 After that, it starts leaking the serial number 711 00:29:51,030 --> 00:29:54,120 from the file in that worst KACE, 712 00:29:54,120 --> 00:29:57,540 which is what was used in virtual box installations. 713 00:29:57,540 --> 00:30:00,660 And so, interesting to note that the timer 714 00:30:00,660 --> 00:30:01,650 that you see spinning 715 00:30:01,650 --> 00:30:06,030 is actually getting spun every time a response comes in 716 00:30:06,030 --> 00:30:08,550 and you can see it hang on the successful guesses 717 00:30:08,550 --> 00:30:10,920 because KACE does a little bit more processing, 718 00:30:10,920 --> 00:30:12,870 so even if we couldn't have made this oracle, 719 00:30:12,870 --> 00:30:14,040 we could have done a timing attack 720 00:30:14,040 --> 00:30:18,090 or maybe some other technique to leak this data. 721 00:30:18,090 --> 00:30:19,980 And so after this finishes, 722 00:30:19,980 --> 00:30:22,260 you're gonna see it run commands as root. 723 00:30:22,260 --> 00:30:25,890 It's actually gonna set up a set UID bash shell 724 00:30:25,890 --> 00:30:27,450 so that I don't have to go through two 725 00:30:27,450 --> 00:30:29,010 command injection contexts, 726 00:30:29,010 --> 00:30:31,410 makes running payloads a little easier 727 00:30:31,410 --> 00:30:33,420 and it's gonna happen really, really quickly. 728 00:30:33,420 --> 00:30:35,940 It's gonna make a new admin, make a script 729 00:30:35,940 --> 00:30:38,580 that gets pushed out to all of the devices in the org 730 00:30:38,580 --> 00:30:40,050 and that script is pretty mundane, 731 00:30:40,050 --> 00:30:41,700 it's just going to change the background 732 00:30:41,700 --> 00:30:45,750 to that cowboy cat picture, but it happens just as quickly. 733 00:30:45,750 --> 00:30:49,200 And in a few seconds, you'll see that device 734 00:30:49,200 --> 00:30:51,690 that I'm running this payload on top of 735 00:30:51,690 --> 00:30:55,020 it's registered in my org and it'll pull down that job, 736 00:30:55,020 --> 00:30:56,970 run it, and the background will change. 737 00:31:02,070 --> 00:31:04,031 All right, and obviously-- 738 00:31:04,031 --> 00:31:07,031 (audience clapping) 739 00:31:11,250 --> 00:31:13,590 So obviously a real attacker wouldn't do that, 740 00:31:13,590 --> 00:31:15,210 they would probably do something more malicious, 741 00:31:15,210 --> 00:31:16,380 like deploy ransomware, 742 00:31:16,380 --> 00:31:18,690 but hopefully that gets the point across. 743 00:31:18,690 --> 00:31:21,240 And so I should say the full exploit for that 744 00:31:21,240 --> 00:31:22,890 is available on my GitHub. 745 00:31:22,890 --> 00:31:26,580 It was also shared with the speaker materials. 746 00:31:26,580 --> 00:31:28,560 I defanged the exploit script a little bit, 747 00:31:28,560 --> 00:31:31,157 it didn't seem so appropriate to release 748 00:31:31,157 --> 00:31:34,173 something that just works like that outta the box, 749 00:31:35,130 --> 00:31:36,750 but it does run commands as root. 750 00:31:36,750 --> 00:31:38,730 And so it's enough to demonstrate 751 00:31:38,730 --> 00:31:41,943 that these vulnerabilities do work like this in practice. 752 00:31:43,380 --> 00:31:44,940 Now, in terms of detecting this, 753 00:31:44,940 --> 00:31:46,410 the general access log for KACE 754 00:31:46,410 --> 00:31:49,740 is actually good enough to catch all of this 755 00:31:49,740 --> 00:31:52,320 because org IDs are supposed to be integers. 756 00:31:52,320 --> 00:31:55,170 And the initial SQL request is-- 757 00:31:55,170 --> 00:31:57,420 SQL injection request is just a get. 758 00:31:57,420 --> 00:31:59,970 So you can filter for any org ID 759 00:31:59,970 --> 00:32:01,290 that's not strictly an integer 760 00:32:01,290 --> 00:32:03,210 and you'll be able to know if the attack 761 00:32:03,210 --> 00:32:04,443 had actually happened. 762 00:32:06,810 --> 00:32:08,220 Now, I have some closing reflections 763 00:32:08,220 --> 00:32:09,810 that I wanna share with you. 764 00:32:09,810 --> 00:32:11,940 So first off, as much as I wish I could say 765 00:32:11,940 --> 00:32:14,880 this was new and novel work, it actually wasn't. 766 00:32:14,880 --> 00:32:17,400 Some firm called Core Security reported 767 00:32:17,400 --> 00:32:19,140 very, very similar vulnerabilities, 768 00:32:19,140 --> 00:32:21,990 if not the same ones in 2018. 769 00:32:21,990 --> 00:32:24,630 And so the SQLi was reported 770 00:32:24,630 --> 00:32:27,960 and I never got to see this unpatched code, 771 00:32:27,960 --> 00:32:29,820 but my best guess is the fix for that 772 00:32:29,820 --> 00:32:33,060 was actually just parameterizing org ID in the first place. 773 00:32:33,060 --> 00:32:35,430 And that totally makes sense to me. 774 00:32:35,430 --> 00:32:36,990 I, through code review, 775 00:32:36,990 --> 00:32:38,940 missed that this would be exploitable too, 776 00:32:38,940 --> 00:32:43,560 so I could see how even the security researchers, Quest, 777 00:32:43,560 --> 00:32:45,120 they all could have missed that. 778 00:32:45,120 --> 00:32:47,330 I definitely would've missed it too if I didn't get lucky, 779 00:32:47,330 --> 00:32:50,220 but it just goes to highlight the importance of explicit 780 00:32:50,220 --> 00:32:51,990 versus implicit sanitization. 781 00:32:51,990 --> 00:32:54,180 If you had done the explicit sanitization, 782 00:32:54,180 --> 00:32:56,580 this weird convenience feature, MariaDB, 783 00:32:56,580 --> 00:32:57,780 would've never mattered. 784 00:32:58,650 --> 00:32:59,834 As far as the RCE, 785 00:32:59,834 --> 00:33:02,250 I'm a little bit more confident that this was new 786 00:33:02,250 --> 00:33:06,390 because the payload that they had shown and described 787 00:33:06,390 --> 00:33:07,980 didn't include this MSI parameter 788 00:33:07,980 --> 00:33:10,350 that was required to hit the vulnerable code path 789 00:33:10,350 --> 00:33:12,480 for my command injection. 790 00:33:12,480 --> 00:33:15,030 And so, regardless of whether or not 791 00:33:15,030 --> 00:33:19,650 it was a reintroduction or maybe regression, 792 00:33:19,650 --> 00:33:22,020 it was probably also assumed that org ID 793 00:33:22,020 --> 00:33:23,970 was never gonna be attack or controlled like that. 794 00:33:23,970 --> 00:33:26,640 Again, that convenience feature in MariaDB 795 00:33:26,640 --> 00:33:28,953 really bit the developers in the butt. 796 00:33:30,840 --> 00:33:34,830 So definitely explicit versus implicit sanitization 797 00:33:34,830 --> 00:33:35,880 is the way to go. 798 00:33:35,880 --> 00:33:39,090 You just remove so much unknown, unknown when you do that 799 00:33:39,090 --> 00:33:40,800 and effectively guarantee 800 00:33:40,800 --> 00:33:43,020 that you'll never have to worry about it, but 801 00:33:43,020 --> 00:33:44,970 both of these vulnerabilities together also, 802 00:33:44,970 --> 00:33:46,710 for me at least, highlighted the importance 803 00:33:46,710 --> 00:33:47,970 of defense in depth. 804 00:33:47,970 --> 00:33:49,560 I never would've been able to read files 805 00:33:49,560 --> 00:33:51,690 if SQL queries weren't being made as root 806 00:33:51,690 --> 00:33:53,670 and the exploitation would've stopped there, 807 00:33:53,670 --> 00:33:56,003 the command injection would've never been reachable. 808 00:33:57,300 --> 00:34:00,120 And so here's the total timeline for the disclosure. 809 00:34:00,120 --> 00:34:03,060 I contacted them on 4/20 and the very next day, 810 00:34:03,060 --> 00:34:04,380 I had a meeting with the Quest team 811 00:34:04,380 --> 00:34:06,681 to explain essentially everything that I found, 812 00:34:06,681 --> 00:34:08,910 they were pretty concerned and responsive, 813 00:34:08,910 --> 00:34:12,281 and they released a hot patch relatively quickly. 814 00:34:12,281 --> 00:34:15,150 And we actually mutually chose together 815 00:34:15,150 --> 00:34:16,920 to withhold the CVEs. 816 00:34:16,920 --> 00:34:19,560 So I haven't talked about any of the CVE numbers, 817 00:34:19,560 --> 00:34:22,290 they aren't out now, but I didn't get a chance to put them 818 00:34:22,290 --> 00:34:24,180 in my speaker materials because they were due 819 00:34:24,180 --> 00:34:26,820 a little bit earlier than the CVEs were released, 820 00:34:26,820 --> 00:34:30,570 but the patch was out on 5/11 and they sent very, 821 00:34:30,570 --> 00:34:33,180 very strongly worded communication to their customers, 822 00:34:33,180 --> 00:34:35,250 consistently urging them to patch. 823 00:34:35,250 --> 00:34:37,920 Patch acceptance was relatively high, 824 00:34:37,920 --> 00:34:39,810 even in those first two weeks. 825 00:34:39,810 --> 00:34:41,760 And the CVEs were withheld essentially 826 00:34:41,760 --> 00:34:43,050 because the app's PHP. 827 00:34:43,050 --> 00:34:45,780 So patch dipping is almost trivial 828 00:34:45,780 --> 00:34:48,840 and it wouldn't really have been fair to those customers 829 00:34:48,840 --> 00:34:51,420 to release this very critical CVE 830 00:34:51,420 --> 00:34:54,162 and have the motivated attackers patched 831 00:34:54,162 --> 00:34:57,573 quicker than they can schedule a regular patch. 832 00:34:59,640 --> 00:35:02,730 And then finally, I just wanna give some credits and thanks. 833 00:35:02,730 --> 00:35:05,220 So again, it really pains me to say this, 834 00:35:05,220 --> 00:35:08,278 but credits to Core Security for beating me to this. 835 00:35:08,278 --> 00:35:10,680 I wasn't guided by their research. 836 00:35:10,680 --> 00:35:15,090 I didn't know about the CVEs until after the fact. 837 00:35:15,090 --> 00:35:18,240 Probably my fault, I would've saved myself some time, 838 00:35:18,240 --> 00:35:20,280 if I hadn't found existing CVEs 839 00:35:20,280 --> 00:35:22,650 before I had started looking into this 840 00:35:22,650 --> 00:35:24,930 and credit to Michal Spacek for 841 00:35:24,930 --> 00:35:28,230 making that pre-computed magic hash value that I used. 842 00:35:28,230 --> 00:35:30,210 And then finally, I just wanna thank Nuro, 843 00:35:30,210 --> 00:35:31,650 which is the company that I work at now 844 00:35:31,650 --> 00:35:34,050 for giving me the opportunity to do such interesting 845 00:35:34,050 --> 00:35:35,400 and impactful work. 846 00:35:35,400 --> 00:35:37,770 And then I just wanna give a huge thanks to John Novak, 847 00:35:37,770 --> 00:35:40,050 who is a former coworker at Praetorian. 848 00:35:40,050 --> 00:35:41,790 He taught me a ton about appliance hacking 849 00:35:41,790 --> 00:35:42,960 showed me some of those neat tricks 850 00:35:42,960 --> 00:35:45,450 that I got to share with you today and by extension, 851 00:35:45,450 --> 00:35:47,340 thanks to Praetorian for just being a great environment 852 00:35:47,340 --> 00:35:48,390 to learn in. 853 00:35:48,390 --> 00:35:49,950 So I will take any questions, 854 00:35:49,950 --> 00:35:52,074 thank you for coming to my talk. 855 00:35:52,074 --> 00:35:55,074 (audience clapping)