SoFunction
Updated on 2025-04-08

Discuz! Forum writing error vulnerability

Writing errors lead to malicious user constructing statements that can be written to the webshell, thereby controlling the entire server.
A few nights ago, I read all the front desk files and variables in database calls. I saw if there were any areas where the filtering was not strict. After reading it, I felt that there were indeed many areas where the filtering was not strict, but they were all protected by single quotes. In php, if the magic_qoute_gpc=on (default) compiler will automatically escape special characters such as single quotes, and it is very difficult for us to change the execution process of the program at this time. This greatly increases the difficulty of invasion and to some extent ensures its safety. This is also why friends have asked that they can still be used when magic_qoute_gpc is on. If a single quote intervention is required to exploit the vulnerability, it is basically useless in the Discuz! forum.
After reading all the database connections in all files, I found nothing valuable. At this time, my thinking began to get a little confused. I was hesitating whether I should try to start from the programmer's thinking logic loopholes. This means that I have to read through all the code and find some things that programmers do not consider when writing programs. This kind of loophole is mostly a contextual logic relationship error, or the limitation is not unique, and there are some things that the administrator should have paid attention to but ignores.
Thinking of this, it seems that there is nothing to be lazy, so I can only read through the code. Since the goal is a logical error, you must look at the context closely, so choose to start reading from the entry point. The entry point should be, because this is the place to log in, and everyone will log in to the forum from here. Come on!
The old rules, let’s first look at a piece of code:
=========codz begin==========   
50 $errorlog = "$username\t".substr($password, 0, 2);   
......   
54 $errorlog .= substr($password, -1)."\t$onlineip\t$timestamp\n";   
55 $password = md5($password);   
56 $query = $db->query("SELECT  as discuz_user,  as discuz_pw, ,  AS styleidmem, ,  
, ,  LIKE '%\t$username\t%' AS specifieduser   
FROM $table_members m LEFT JOIN $table_usergroups u ON  LIKE '%\t$username\t%' OR (= AND ((='0' AND ='0' AND ='') OR (>= AND  username='$username' AND password='$password' ORDER BY specifieduser DESC");   
......   
69 if(!$discuz_user)   
{   
70 @$fp = fopen($discuz_root.'./forumdata/', 'a');   
71 @flock($fp, 3);   
72 @fwrite($fp, $errorlog);   
73 @fclose($fp);   
74 showmessage('login_invalid', '');   
}   
=========codz endz==============   
Let’s take a look at this code one by one. He first records the username and password entered. The password is only the first two digits, and then the next digit of the password is taken, and the IP and time are assigned together. Then go to the md5 value of the password and put it in the database. If you think we can change the operation flow of the database execution statement, that's wrong. We have nothing to do in the face of single quotes (at least I can't do anything unless encrypted). If the username and password are incorrect, the wrong username and password will be recorded. During the entire process of recording the wrong password, the variable has not been verified, which means that if I deliberately enter the wrong username, he will not check and record it directly. Then if my wrong username is an executable code, it will also record it. After he records it, we call this file and form a shell.
Are you already excited by this? Sorry, your excitement is invalid. I was not excited at all because when I read the first time before, I paid special attention to the operation of Discuz!'s file handle. It does not filter individual variables, but when it initialized it, it has added a sentence to the starting point of all the data files ending with .php: This is written during initialization, and we should all understand the function of this sentence. You can't call what you wrote because it's over at the beginning. This obviously won't succeed. I'm a little annoyed and thought to myself, aren't it just 5 sheep sticks? If you lose, you lose! I threw down the code in anger, ran to my grandma's room, hugged my grandma and acted coquettishly (acting coquettish, cheating, and making trouble in front of my grandma is one of my favorite things). I repeatedly cursed Discuz!'s pervert in front of my grandma, saying how I read the code seriously. Grandma didn't know what I was talking about, didn't care about me making trouble for her, and continued to watch her own TV. After a while, grandma responded: "You kid, you are just careless and not careful at all. Look at what code you have (that is probably talking about the code) is broken again." I crawled back to my room with a loud laugh. Sitting in front of the computer, I drank a glass of boiled water, and calmed down.
It is very necessary to be careful, persevere, self-study, and good at summarizing. I went back and read the file again to see if there was anything I ignored. The file is not long, and I still feel that it is still fine after reading it again. Since there is no problem here, let’s put the idea on that sentence. How is this sentence written?
So I flipped through this code:
==========codz begin==========   
29 function loginit($log) {   
30 echo 'Initialization Record'.$log;
31 $fp = @fopen('./forumdata/');   
32 @fwrite($fp, "\n");   
33 @fclose($fp);   
34 result();   
35 }   
......   
1389 loginit('karmalog');   
1390 loginit('illegallog');   
1391 loginit('modslog');   
1392 loginit('cplog');   
1393 dir_clear('./forumdata/templates');   
1394 dir_clear('./forumdata/cache');   
==========codz endz==========   
Obviously, the loginit function writes this sentence into it. In this way, even if we write the code, we will not be able to call and execute because of the existence of that sentence. We've completely entered a dead end. But I seem to think there is something wrong with this code. After taking a closer look, why is that fopen so unpleasant? If you remember correctly, the syntax format of fopen should be like this:
resource fopen ( string filename, string mode [, int use_include_path [, resource zcontext]])   
The first two parameters are a file handle, or specify which file to open. The second parameter specifies the way to open, such as read or write. What I want to remind you here is that these two parameters are required. For example, when we write to the directory, our statement is written like this: fopen('./','w'), the following opening method must be selected, otherwise an error will be reported. But we noticed that there is only one parameter in his code, so how can the program be executed correctly?
Let's do an experiment, create one, and write the following code:
===========codz begin==========   
$fp = @fopen('./');   
@fwrite($fp, "\n");   
@fclose($fp);   
echo "success!";   
===========codz endz============   
Here is an environment like in Discuz!. If the program executes successfully, one will be generated in the root directory, and the beginning line should be, and success is displayed on the screen. In fact, success here is used to let us understand the location where the program is executed. We submit in the url: http://127.0.0.1/myhome/, success is displayed on the screen, which means that it has been executed to the end of the program. However, when checking the directory, it was found that no file named was generated, which means that our writing failed. Maybe everyone will be surprised that there should be error information if the write failed. It should be true, but because @ is added before fopen, @ is used in php to suppress all error messages generated by calling functions. In other words, even if something goes wrong, it will not report an error.
Grandma taught her a lesson, I was too careless!
Assuming everything follows my ideas, that is, when installing initialization, because of the wrong use of fopen, a file containing code will never be generated in the discuz/forumdata directory. However, because of suppressing error information, the initialization will still be displayed during installation, but in fact, it has not been initialized, and it has not been generated. And if there is no initialization here, it means that the generation and initialization will be completed in. The initialization does not write any protection statements or filtering measures to the file to avoid user calls. At this point, everything is clear. To put it bluntly, because of the file error initialization, we can write malicious code and then call that file to generate a shell to control the entire website.


Exploit code is as follows
The following content may be offensive and is for security research and teaching purposes only. Users are at their own risk

You don’t need to register any account. Go to the login page. Enter 123456 first in the login username and press Enter. You may understand here that the first two digits of the password are displayed, so what is saved in it is:
*****6 127.0.0.1 1022383175   

This allows you to check the settings of php. Let’s first check whether the settings of register_globals are on. (Most of the websites are on) Good, then we enter it in the login port, it is best not to use system() here. When I was doing the test, many websites disabled this system() function.
Then we call http://192.168.0.13/forumdata/?cmd=dir
There is a bunch of spam information coming out in front. Looking at the bottom, can you see that the directory is listed? However, writing this way is a bit troublesome, because during the experiment, there were more than 100,000 registered users on large websites, so the file will be surprisingly large and the opening speed will be extremely slow.
So we can actually write it like this here. We change the php shell to a jpg format image and upload it to the host.
Called in the URL:
http://192.168.0.13/forumdata/...chments/   
Then we submit the URL directly:
http://192.168.0.13/attachments/ is just?...It's just a shell!