Analyzing and Fixing a Basic-Authentication Phishing Vulnerability in Discuz! 3.2

SummaryAnalysis. Before examining the vulnerability, review the background. 401 phishing, also called basic-authentication phishing, relies on the fact that most web servers allow anonymous access. Users normally retrieve public website content without entering authentication credentials. Nginx and Apache…

DiscuzBasic-Authentication Phishing Vulnerability

Analysis

First, review the necessary background.

401 phishing, also called basic-authentication phishing, relies on the fact that most web servers allow anonymous access. Users normally retrieve public website content without entering a username or password.

Nginx and Apache allow anonymous access by default. To require authentication in Nginx, configure /etc/nginx/sites-enabled/default as follows:

server {
    server_name blog.cnpanda.net
    root /www/panda

   # ...

   location / {
        # the two lines below require non-anonymous access
        auth_basic "Restricted";
        auth_basic_user_file htpasswd;
        # ...
    }

   # ...
}

Then create an htpasswd file; those standard steps are omitted here.

Apache is simpler; its default httpd.conf configuration is:

<directory "/etc/www">
 Options IndexesFollowSymLinks Includes
 AllowOverride None
 Order allow,deny
 Allow from all
</Directory>

To require non-anonymous access, the server sends a WWW-Authenticate header. The following Header() call requests BASIC authentication from the client:

header("WWW-Authenticate:BASIC Realm=My Realm");

It adds a WWW-Authenticate field to the HTTP response headers. That field is required for basic-authentication phishing. The following analysis uses the latest Discuz! 3.2 release.

The issue is in imgtag in source/function/function_editor.php:

function imgtag($attributes) {
	$value = array('src' => '', 'width' => '', 'height' => '');
	preg_match_all("/(src|width|height)=([|_)([^']+)(\2)/is", dstripslashes($attributes), $matches);
	if(is_array($matches[1])) {
		foreach($matches[1] as $key => $attribute) {
			$value[strtolower($attribute)] = $matches[3][$key];
		}
	}
	@extract($value);
	if(!preg_match("/^http:/i", $src)) {
		$src = absoluteurl($src);
	}
	return $src ? ($width && $height ? '[img='.$width.','.$height.']'.$src.'[/img]' : '[img]'.$src.'[/img]') : '';
}

The function first validates width and height, then checks whether the requested URL is a subdirectory path:

function absoluteurl($url) {
	global $_G;
	if($url{0} == '/') {
		return 'http://'.$_SERVER['HTTP_HOST'].$url;
	} else {
		return $_G['siteurl'].$url;
	}
}

The function finally returns [img] + URL + [/img]. It validates only the URL path and never confirms that the target is an image, so arbitrary URLs are parsed. A PoC can be constructed as follows: Please Login — Information

ERROR!!

	You do not have permission to access this page!
	<br>
	Click here to <input type="button"  value="GO back" onclick="history.go(-1)">
</body>
<html>

The PoC records the username and password entered by the user in Panda.txt. Upload it to a local server at:http://localhost/401.php Use the PoC:

1.png
2.png

A dialog appears. Any credentials entered by the user are recorded in panda.txt:

3.png

The flaw exists not only in posts and replies but also in profile home pages and signature fields.

4.png
5.png

In short, any Discuz location that parses [img] tags has this issue. Some dismiss the vulnerability because technical users seem unlikely to fall for it, but phishing primarily targets ordinary users. Even a technical user may be induced to inspect a PoC URL, after which additional behavior such as XSS tests could expose unexpected information—for example, page source:

6.png
7.png

Or browser cookies left behind:

8.png

Mitigation

Option 1: validate the URL in imgtag in source/function/function_editor.php. This is the simplest fix but can slow page rendering. Option 2: reject responses with status 400 or higher, or filter [img] content. The second is recommended. Add a repair function around line 204 of source/module/forum/forum_post.php:

9.png
$message = isset($_GET['message']) ? Repair_401(censor($_GET['message'])) : '';

Repair_401 can be added at the end of forum_post.php:

10.png
function Repair_401($message){
	preg_match_all('|[img](.*)[/img]|U',$message,$matches);	//匹配所有img标签
		if(!empty($matches)){		//存在匹配结果
 			$img = $matches[1];
 		foreach($img as $val){
 			$fstr = substr($val,0,10);
 			$src = strpos($fstr,'://') < 1 ? "http://".$val : $val;
 			if(!@fopen($src, 'r' ) ){	//图片无效
 				showmessage('图片格式错误');
 			}
 		}
	}
}

This fixes basic-authentication phishing in posts and replies, but not in profile pages, biographies, or signatures. Add Repair_401 to the corresponding code or disable [img] parsing from the administration panel. The final result is shown below:

11.png

Conclusion

The issue is not limited to Discuz. Any image URL parser that does not validate the target may be vulnerable. Browser behavior matters: Firefox triggered the issue in testing while Chrome did not, so real exploitation depends on the environment.

Afterword

The mitigation above is incomplete. A researcher from 90sec noted that an attacker can configure their server to parse an image file as PHP and bypass it. A better defense is to stop fetching a resource when the response status is 401, or simply prohibit third-party resources. The latter is the cleanest and safest option.

References

[] goderci@YunDay, 'Analyzing Basic-Authentication Phishing' [] Configuring Password-Protected Access in Nginx [] Using XSS on me.jd.com for Basic-Authentication Phishing [] Basic-Authentication Phishing on Any Site That Embeds External Content