CVE-2023-28854 (published 2023-04-03T18:15Z)

nophp is a PHP web framework. Prior to version 0.0.1, nophp is vulnerable to shell command injection on httpd user. A patch was made available at commit e5409aa2d441789cbb35f6b119bef97ecc3986aa on 2023-03-30. Users should update index.php to 2023-03-30 or later or, as a workaround, add a function such as `env_patchsample230330.php` to env.php.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-77
cwe_name: Improper Neutralization of Special Elements used in a Command ('Command Injection')
description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
extended_description: 
            Many protocols and products have their own custom command language. While OS or shell command strings are frequently discovered and targeted, developers may not realize that these other command languages might also be vulnerable to attacks.
         
         

Fix information:
repo_url: https://github.com/paijp/nophp
commit_details: repo_url: https://github.com/paijp/nophp
author: paijp
author_date: 2023-03-30 13:05:12+09:00
msg: Security fix: command injection when send a password setting mail.
repository: repo_url: https://github.com/paijp/nophp
repo_name: paijp/nophp
repo_language: PHP
owner: paijp
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,7 @@
 
 ## Important changes.
 
+- 2023/03/30 **Important security fix**. Please see env_patchsample230330a.php .
 - 2023/03/05 The argument '$par' in recordholder::__construct() is no longer parsed with parsewithbq(). Please parse argument in constructor in user class.
 - 2022/07/11 Return value of commandparser::parsehtml() is discarded. Please call commandparser::output() for output.
 - 2022/07/11 debuglog directory changed to 'debuglog???????*'. Please create a directory with at least 7 characters that is hard to guess after 'debuglog'.

programming_language: Markdown
filename: env_patchsample230330a.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,133 @@
+<?php
+
+#
+# ======== 2023/03/30: Security patch sample. ======== 
+#
+# What is this security risk type?
+#	- Shell command injection on httpd user.
+#
+# At what point does this security risk become apparent?
+#	- Send password setting mail or mail-login mail.
+#	- Maybe many system have a 'forget password?' link.
+#
+# Who can use this security risk?
+#	- Users who can add or modify the login field in login table to invalid mail address.
+#	- In many system, admin user will be able to create or modify a user.
+#	- If your system allow users to change login name to invalid mail address, the user can use this security risk. 
+#
+# How to fix it?
+#	- Please use the newest index.php or add this bq_login() function to your env.php.
+#
+# Important line is here:
+# <			if (!preg_match('/^[0-9A-Za-z][-_.@+0-9A-Za-z]*$/', $login))
+# <				log_die("invalid character in login.");
+#
+
+function	bq_login($type = null, $r = null)
+{
+	global	$tablelist;
+	global	$sys;
+	
+	if ($type === null)
+		return array();
+	switch ($type) {
+		case	"emptypass":		# empty password
+			header("Location: {$sys->url}?mode=2mailsent");
+			if (($mailcmd = $sys->mailcmd) == "")
+				log_die("mailcmd empty.");
+			if (($mailbody = @$sys->mailbody) == "")
+				log_die("mailbody empty.");
+			if (@$sys->mailinterval <= 0)
+				log_die("mailinterval <= 0.");
+	
+			if (($login = @$_POST["login"]) == "")
+				log_die("login empty.");
+			if (!preg_match('/^[0-9A-Za-z][-_.@+0-9A-Za-z]*$/', $login))
+				log_die("invalid character in login.");
+			$list = $tablelist["login"]->getrecordidlist("where login = ?", array($login));
+			if (count($list) < 1)
+				log_die("no login found");
+			$r = $tablelist["login"]->getrecord($uid = $list[0]);
+	
+			if (@$r->v_mailsent > $sys->now - $sys->mailinterval)
+				log_die("mail interval too short.");
+	
+			$key =$r->getrandom();
+			$r->setmailkey($key);
+	
+			$a0 = array("@addr@", "@url@");
+			$a1 = array($login, "{$sys->url}?mode=1login&uid={$uid}&key={$key}&");
+			$mailcmd = str_replace($a0, $a1, $mailcmd);
+			$mailbody = str_replace($a0, $a1, $mailbody);
+			if (($fp = popen($mailcmd, "w")) === null)
+				log_die("popen failed: {$mailcmd}");
+			fputs($fp, $mailbody);
+			pclose($fp);
+			log_die("mail sent.");
+		case	"goodlogin":		# login success
+			break;
+		case	"badlogin":		# login fail
+			break;
+		case	"logout":		# logout
+			break;
+		case	"changelogin":	# change mail address
+			break;
+		case	"changepass":		# change password
+			break;
+	}
+	return array();
+}
+
+
+#
+# ======== Original env.php ========
+#
+
+
+date_default_timezone_set("Asia/Tokyo");
+putenv("LANG=ja_JP.UTF-8");		# for sendmail.php
+
+#
+# In first time, this user will be created.
+#
+
+$sys->defaultuser = "nobody@something.com";
+
+## If you cant use mail(for setting password), use this.
+# $sys->defaultpass = "sample";
+
+#
+# Filesystem settings.
+#
+
+$sys->htmlbase = "./res/";
+$sys->sqlpath = "sqlite:/var/www/db/nophp.sq3";
+# $sys->sqlpath = "mysql:dbname=test";		# sorry, not tested.
+
+$sys->rootpage = "g9999";
+
+#
+# If you want to use 'index.php?mode=sql', you have to set this basic authentication.
+# $sys->auth_pass = sha1( $sys->auth_salt . $pass );
+#
+
+$sys->auth_user = "admin";
+$sys->auth_salt = "235ba225813f156690b798dc978099e1e76441ee";
+# $sys->auth_pass = "";
+
+#
+# If the password on the login form is empty, a mail with URL will be sent.
+#
+
+$sys->mailcmd = "mail -s 'login URL.' @addr@";
+$sys->mailbody = "@url@";
+
+#
+# You can send a report to slack with debuglog URL.
+#
+
+# $sys->reportjsonurl = "https://hooks.slack.com/services/9999";
+$sys->reportjsonbase = array("text" => "@body@\n<@link@>");
+
+
+?>

programming_language: Perl
filename: index.php
change_type: ModificationType.MODIFY
diff: @@ -3683,6 +3683,8 @@ if (!function_exists("bq_login")) {
 		
 				if (($login = @$_POST["login"]) == "")
 					log_die("login empty.");
+				if (!preg_match('/^[0-9A-Za-z][-_.@+0-9A-Za-z]*$/', $login))
+					log_die("invalid character in login.");
 				$list = $tablelist["login"]->getrecordidlist("where login = ?", array($login));
 				if (count($list) < 1)
 					log_die("no login found");

programming_language: PHP