Leaked .git folder leads to RCE

Today I wanted to share my first big success story from my bug bounty attempts. Although the issue has been fixed, the report has not been officially disclosed yet. Therefore, the target today will be everyone’s favorite “”.

While my methodology for approaching a new target seemingly evolves every time I go through it, one constant has been a tool called reNgine for initial recon. I set it up on a VPS so that way I can remotely access it from my home PC while offloading all of the scanning. It gives you a GUI to add domains as a target, run basic scans to detect subdomains, ports, run Aquatone, and (most importantly for todays writeup) run a simple dirsearch.

ReNgine GUI – note the accessible git artifacts

As you can see, reNgine is great for doing high level analysis of a domain. All of the results including the technology tags and directory results are searchable to help you narrow down what you’re looking for. In this case, the git folder and artifacts immediately caught my eye.

Based off of the endpoints found, it looks like this is part of a git repo or commits to a repo saved locally. I didn’t really know what I was looking at, so I started googling about reconstructing a git repo using these artifacts. I could see timestamps, hashes, commit messages and other information, but I was struggling to put it all together.

I came across an article called Learing the internals of git by hacking websites which sounded exactly like what I was looking for. It takes you step by step through following the chain of the git structure and recovering files based off of the links and file hashes. I went through one successfully, but it took too long for my liking. So naturally, I found something someone else wrote to do it for me! Enter: GitHack. I cant read the whole description because its in a different language, but I do know that I run it with a URL and the black magic happens to get me source code. This is where I say to the camera “Im in” after 15 seconds of typing.

GitHack retrieves what appears to be full contents of WordPress site

The resulting folder that was created gave me what appeared to be the entire WordPress site. It wasn’t an exact one-to-one copy of the live version, but it was definitely close. Included were all the themes, plugins, and most importantly: configuration files.

Config file with database name, account, and password

Whoever committed these files was kind enough to include wp-config.php, which includes the credentials to connect to the database. I asked myself “surely, it cant be this easy… right?”. I fully expected them to be old or incorrect, but nevertheless tried to find a way to use them while not getting my hopes up.

I had another stroke of luck that I found the site had Adminer installed. I have some experience with phpmyadmin, and Adminer functions in a similar way – its a GUI to access and modify databases on the web server. I tried the credentials and to my pleasant surprise they worked!

There seemed to be several generations of WordPress tables on the server for several different versions. I didn’t get an exact count but it was in the magnitude of tens to hundreds. Luckily, I was able to find wp_users which gave me all user accounts, hashed passwords, and emails. More importantly, I could edit all the entries in the table.

Mom get the camera!

At this point, I had a couple different options. For starters, I could mess with the users table to try and give access to myself. This could be either adding a new user to the table manually and then granting the account admin rights, or editing the password hash of the admin user. Once I have admin rights, I could proceed with exploiting one of several vulnerable plugins that required authentication or start uploading arbitrary files, whatever I wanted. Odds are, once you have admin rights there’s a way to escalate it to RCE given the outdated WordPress site. That being said, it requires editing a production database and potentially locking out the administrator account – not something I was looking to screw with.

Fortunately, as I highlighted in the top left of the above screenshot, adminer comes with a GUI to run SQL commands against the database. So boom, arbitrary SQL command execution. There are several ways to use this to access files on the local system and get true command execution. Unfortunately, I did not get a screenshot of the interface and the test commands I ran, mainly because I was frantically writing a report to let the program know the activity was my doing before they started wondering why some idiot was banging away at their system.

In my report, I outlined both paths to achieve RCE from the point I could verify on my own – adminer interface and database access. The program asked me to not make any changes to the data and promptly updated the credentials to kick me out as well. They also verified that both paths could most likely have been successful – good enough for me!

While not the first report I’ve had triaged, its the first one that I felt like I actually did something for. Instead of just finding an exposed endpoint and reporting I could get there, I was able to take that information and escalate it. My tips for anyone out there would be enumeration and escalation. Find out everything you can about the target, and then take that information and ask yourself over and over “what can I do with this?”. I know I’m not the first person to say either of those, but if I just saw /.git returned 200 with no content and didn’t look any further, I would have missed the whole chain. Persistence is key!

Happy hunting to all you bug hunters out there!


More Information Disclosure in Wavlink Devices: CVE-2020-10973, CVE-2020-10974, and CVE-2020-12266

After my previous adventures with a Wavlink router led to Remote Code Execution, I decided to pivot my focus. I started by looking at what else was exposed on the router, and then purchased two other Wavlink devices to see if they had the same issues. I got two Wifi extenders, the WL-WN579G3 and the WL-WN579A3. What I found was not a surprise – they were just about as vulnerable as the router I started with. In fact, with the exception of the page that had the plaintext username/password, every single live_(string) page seemed almost exactly alike across all three devices. Some of the wording or specific information was different, but the meat and potatoes was always the same when comparing the page on all three devices

One of the first things I noted on the Wavlink router was the repeated calls to live_(string) endpoints. After I discovered that they could be accessed without any authentication, I decided to go through each page one by one to see what could be accessed.

Many of the pages I could not find a rhyme or reason for – there is a lot of duplicate information that I feel could be easily retrieved without serving it on a web page. Perhaps it was the developers intent to make all this information accessible to the world, or maybe they just forgot to clean some things up. Who knows? But enough of the speculation – lets get into the good stuff. Just a reminder – every screenshot below is of a web page that is externally accessible without requiring authentication and contains sensitive data.

Lets start with the basics. If you recall CVE-2020-10971 aka my previous post, you can achieve RCE if there is an authenticated user at the time. Instead of having to guess if there is a user logged in, you can get that information yourself by simply going to a web page.

Basic status info

The “userlogin” piece corresponds to how many active sessions there are at the point in time – as you can see, when I viewed this page I did not have an active session.

Another page gives specific device info, such as model, IP address, and MAC address

More device details

But it gets better. Want a summary of of the slabinfo of the device? Look no further then the output of a script on one of these pages.

All sorts of fun memory stuff

But wait… theres more! How about a summary of the nearby networks, including name, BSSID, Security, and signal strength?

Not impressed? Check out this set of more in depth information that I didn’t do anything with.

You know what, that one was boring. Lets make up for it by looking at my home devices with their hostname, IP, and MAC address thanks to their DHCP leases.

Want to get more in depth and look at every running process on the device? Say no more.

The best part is, the functionality to backup the configuration of the device does not use any authentication. For the router, it at least encrypts the backup file before sending it. However, you can achieve full RCE on the router without the backup file, and once you have access to the back end you can find out for yourself that it was encrypted with a hard coded, very short key.

Config file being created, encrypted, and returned

For the extenders though its open season. The backup functionality works differently on the backend and also has no encryption whatsoever. With a simple post request, you can retrieve the entire configuration of the device (hundreds of options that I didnt bother playing with). The most important lines are right near the top, where it gives you the username and password in plain text.

My super secret password for the world to see

So while the path is slightly different, we still have a way to get the username and password without any authentication, which can be combined with CVE-2020-10971 to achieve Remote Code Execution by creating a session. The various information disclosure vulnerabilities that I mention in this post are all included in CVE-2020-10973, CVE-2020-10974, and CVE-2020-12266. Once again, Wavlink did not respond to any of my attempts at communication. After just a small amount of testing, the main thought I have about my Wavlink devices is that Im looking forward to never plugging them in again.


2/19/2020 – Initial discovery and notification
2/29/2020 – Follow up notification after no response
3/23/2020 – Follow up notification after no response indicating plans to submit for CVE
4/3/2020 – Additional communication attempts failed to get response, CVE request submitted
5/7/2020 – CVE published
6/30/2020 – Additional details published


Multiple Vulnerabilities in Wavlink Router leads to Unauthenticated RCE – CVE-2020-10971 and CVE-2020-10972

With everyday household items becoming “smart” and connected to the internet, I was interested in seeing how much effort companies were putting into security. I decided it would be a great hobby to buy cheap Chinese technology off of Amazon and see what I could find out.

After searching for routers, I found one from a company called Wavlink that was only $30 off of Amazon. The Wavlink WL-WN530HG4 is an Amazon’s Choice Router with “WPA/WPA2 PSK Mixed security and industry level password encryption”. Additionally, they say “We are fully confident in the design and durability of our products. If you have any issues, please do not hesitate to contact us anytime, night or day : -) “. While the WiFi password might have “industry level password encryption” it’s like putting a very secure padlock on a two foot high fence and the key is under the pot right next to it. But first, lets talk about how I came to that conclusion.

Initially setting up the router was one of the most difficult parts of achieving RCE. Maybe it has to do with my convoluted internet setup in my room at home (Modem -> WiFi Router Downstairs -> Powerline Ethernet Adapter -> Switch in my room -> Wavlink Router) but it kept redirecting me to a page that was half in Chinese telling me I wasn’t connecting directly to the router, even though I was going straight to its IP address. Either way, I did eventually get it set up and started exploring.

I was running Burp while I was doing the initial setup, and one of the things I noticed right away was that after I logged in and was viewing the home page, it looked like calls were being automatically made to a few endpoints to update stats.

Automatic requests while logged into the router

The endpoints all followed the format “live_(string).shtml” and had what appeared to be some kind of session id called “r” that was being sent with the request. Just for fun, I tried going directly to one of the pages without the “r” parameter and I was still able to view the response. Even after I logged out and could not get to the main page, the “live_(string).shtml” endpoints were all still reachable – it appeared no authentication or session ID was required to get there.

live_speed.shtml returns incormation without potential ID as parameter

Now that I had a basic feel for how the router worked, I decided to take it apart. Serial pins is something that has interested me ever since I first heard about them. The idea of a free backdoor is just too juicey for me to pass up. Plus, finding a way to get into a device remotely is much easier when you start from the inside.

Turns out, trying to take the router apart was the second hardest part about achieving RCE – there was a lot of little plastic clips holding the top on, and I missed two screws hidden under a label which was preventing me from making any significant progress. After 15 minutes of prying I realized what I had missed, and I finally had my first look at the board.

The router before I tried to demolish it
It took me way to long to find these bad boys
Finally got the lid off. Immediately noticed the groups of pins at the top

I had my multi-meter and jtagulator at the ready, but fortunately Wavlink was kind enough to label the serial pins for me.

TX, RX, GND, and VCC identified in 2 seconds flat

I soldered on a couple of wires, and using my Attify badge and this handy baudrate script, I was able to establish a serial connection with minicom.

RX TX and GND connected to the Attify Badge and then to my PC
Using the baudrate script – activity seen right away, cleartext identifies baudrate as 57600

Minicom session gets a prompt

At the end of the bootup I was presented with a “WAVLINK login:” prompt. I tried using several normal usernames like root or admin with the password I set in the gui, but nothing worked. I was a bit dissapointed, but after I looked back through the boot logs I found this line about a password change that always appeared:

First mention of this user – no username is entered in the GUI

I decided to try logging in with “admin2860” and the gui password and boom – I was in. It appeared to have a version of BusyBox running on the router. The serial connection was a success, but super annoying to use because whenever you try and type or view a file it decides to spit out a log for some activity happening on the router. Fortunately, it has a built in telnet binary. After starting it, i was able to connect on a port of my choosing with admin2860 and not worry about logs screwing up my view.

Successful login via serial connection

Now that I had a solid connection, I started to explore what webpages were available to the end user. The web directory appeared to be /etc_ro/lighttpd/www/ and listed way more webpages then I had uncovered in my manual exploration. Besides the first few endpoints that were automatically hit, I found tons of “live_(string).shtml” pages that did not require any sort of authentication to get to. And this is where the fun begins.

One of the first pages I checked in the GUI had the below interface. It appeared to be a way to pause some functionality for testing purposes – and of course, no authentication is required to get to it.

It did have an input for your password, so I thought that maybe there was some security on this thing after all. I tried to compare the authentication method with the main login page and boy was I surprised by what I found.

Basic string comparison for the password – admpass is the input text box

Instead of sending my password off to be verified on the back end, it appeared to be checked against a local variable. Curious as to how this was done, I did a quick search for “password” and I literally laughed out loud as to what I found.

Input password compared against plaintext variable

Low and behold, there was my super secret password in plain text, with the admin username in plain text, on a page that requires no authentication of any kind to view. Considering how they already have a built out authentication process used for other pages, its very curious that a developer would go out of their way to set up something different for this one page in such a blatantly insecure manner. Thanks to my handy backdoor, I was able to find that a command is executed on the backend to populate that syspasswd variable every time this page is loaded – that way in case you change the password, you don’t have to worry about this page being out of date.

Running the command myself returns the current password, in plaintext

So what does that get us from the perspective of a remote attacker? We have the ability to get the current admin credentials, and we can get a shell if the telnet binary is started. However, most remote attackers wont be able to solder on any wires, so I wasn’t going to stop there.

Going through the rest of the pages in the www directory, there is another web page that provides this interface:

A built in GUI for command execution as root – could it be that easy?

Obviously this immediately grabbed my attention. I tried a simple “ls” command and the page redirected me to /cgi-bin/(null) and errored out. This was a disappointing result, but when I copy-pasted the address back in to relook at it, I found that the command had appeared to actually execute and it returned the results.

The output of my sample ls command

It appears that the command is executed as the admin2860 user in the /etc_ro/lighttpd/www/cgi-bin directory, and although the redirect feature is broken the command is still executed. I was pleasantly surprised that this page was not accessible without being logged in. Of course, since the admin credentials are already exposed this doesn’t buy you much security – by just going to one other page, you can find the info to log in and execute commands.

Additionally, I stumbled across an interesting fact. While you cannot access the web page for the Command Execution interface, you can still make a POST request to adm.cgi. If it’s in the proper format, and a user is logged in, then the command is executed. While the router appears to do some verification to ensure that someone is authenticated at that time, it makes no discernible effort to verify that the POST request actually came from the user that is authenticated. So even if the endpoint with the plaintext credentials is removed, an attacker could just make a loop of post requests to adm.cgi and as soon as a user logged in they would achieve command execution.

In conclusion, a remote attacker can achieve RCE via a POST request to adm.cgi. There are several conditions required, including proper parameters and an active session. However, these conditions can all be met without any initial authentication required thanks to several specific exposed “live_(string).shtml” endpoints – so an attacker with the right background information about the device could achieve RCE fairly easily.

I did not go very in depth about maintaining persistence on the device outside of the initial exploit chain. I tried creating additional users but they appear to be removed on boot – I believe I could circumvent this with enough time, but due to the already exposed flaws I didn’t feel it was worthwhile spending the time on it.

I attempted multiple times to contact Wavlink about the issues I found via several different support contacts on their site but I was never able to get a response from them. After weeks of no communication, I submitted a report to Mitre and CVE-2020-10971 and CVE-2020-10972 were created, covering this device.


2/19/2020 – Initial discovery and notification
2/29/2020 – Follow up notification after no response
3/23/2020 – Follow up notification after no response indicating plans to submit for CVE
4/3/2020 – Additional communication attempts failed to get response, CVE request submitted
5/7/2020 – CVE published
6/30/2020 – Additional details published