Issues (21)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/HtmlUtils.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Ridibooks\Platform\Common;
4
5
use Ridibooks\Exception\MsgException;
6
7
class HtmlUtils
8
{
9
	public static $cms_allowable_tags = [
10
		'strong' => [],
11
		'b' => [],
12
		'bold' => [],
13
		'u' => [],
14
		'br' => [],
15
		'font' => ['color'],
16
		'a' => ['href', 'target'],
17
		'img' => ['src'],
18
		'video' => ['src'],
19
		'em' => [],
20
	];
21
22
	public static $cp_allowable_tags = [
23
		'strong' => []
24
	];
25
26
	private static $tags_open_and_close_in_one = [
27
		'img',
28
		'video'
29
	];
30
31
	/**
32
	 * @param $html
33
	 * @param array $allowable_tags
34
	 * @return bool
35
	 */
36
	public static function isValidHtmlTag($html, array $allowable_tags)
37
	{
38
		try {
39
			self::assertValidHtmlTag($html, $allowable_tags);
40
		} catch (MsgException $exception) {
0 ignored issues
show
The class Ridibooks\Exception\MsgException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
41
			return false;
42
		}
43
		return true;
44
	}
45
46
	/**
47
	 * @param $html
48
	 * @param array $allowable_tags
49
	 * @throws MsgException
50
	 */
51
	public static function assertValidHtmlTag($html, array $allowable_tags)
52
	{
53
		$html = self::filterNonAllowableTags($html, $allowable_tags);
54
55
		$stack = array();
56
		preg_replace_callback(
57
			'/\<(\/?)([a-z]\w*)([^\>]*)\>/i',
58
			function ($args) use (&$stack) {
59
				$tag_opened = (strlen($args[1]) == 0);
60
				$tag = $args[2];
61
				$attr = $args[3];
62
				$full_tag = $args[0];
63
64
				//br태그는 무시
65
				if ($tag == 'br') {
66
					return;
67
				}
68
69
				if ($tag_opened) {
70
					/*
71
					 * attr의 끝이 / 인것은 무시한다
72
					 *  - <video src='xxxxx'/>
73
					 *  - <div />
74
					 *  - <input />
75
					 */
76
					if ($attr[strlen($attr) - 1] == '/') {
77
						return;
78
					}
79
					array_push($stack, array($tag, $full_tag));
80
				} else {
81
					if (count($stack)) {
82
						$last_tags = array_pop($stack);
83
						$last_tag = $last_tags[0];
84
						$last_full_tag = $last_tags[1];
85
					} else {
86
						$last_tags = null;
0 ignored issues
show
$last_tags is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
87
						$last_tag = null;
88
						$last_full_tag = null;
89
					}
90
					if ($last_tag != $tag) {
91
						throw new MsgException(
92
							'tag mismatch : ' . $last_full_tag . ' => ' . $full_tag
93
						);
94
					}
95
				}
96
			},
97
			$html
98
		);
99
		if (!empty($stack)) {
100
			throw new MsgException(
101
				'tag mismatch : 열리거나 닫히기만 한 태그가 존재합니다.'
102
			);
103
		}
104
	}
105
106
	/**
107
	 * 허용하지 않는 태그용 문자를 변환해준다.
108
	 * @param $string
109
	 * @param array $allowable_tags
110
	 * $allowable_tags 배열 형식:
111
	 * $allowable_tags = [
112
	 * 		'tag' => ['attr1', 'attr2']
113
	 * ]
114
	 * @return string
115
	 */
116
	public static function filterNonAllowableTags($string, array $allowable_tags)
117
	{
118
		// br태그는 개행으로 자동변경
119
		$string = preg_replace('/(\<\/?\s*br[^\>]*\>)/is', "\n", $string);
120
121
		// 태그 사용을 원천 방지하기 위하여 일단은 무조건 &lt, &gt로 변환
122
		$string = str_replace('<', '&lt;', $string);
123
		$string = str_replace('>', '&gt;', $string);
124
125
		$offset = 0;
126
		preg_match_all("/&lt;((?:(?!&lt;|&gt;).)*)&gt;/isU", $string, $matches, PREG_OFFSET_CAPTURE);
127
		foreach ($matches[0] as $index => $match) {
128
			$replace = '';
129
130
			// 원본 전체 문자열
131
			$original = $matches[0][$index][0];
132
133
			// 내부 문자열
134
			$tag_and_attr_string = $matches[1][$index][0];
135
136
			// attr가 있어야되는 것들과 없어야되는 것들의 분리
137
			$tags_without_attrs = [];
138
			$tags_with_attrs = [];
139
			foreach ($allowable_tags as $tag => $attrs) {
140
				if (empty($attrs)) {
141
					$tags_without_attrs[] = $tag;
142
				} else {
143
					$tags_with_attrs[] = $tag;
144
				}
145
146
				// close 태그는 무조건 attr가 없어야됨
147
				$tags_without_attrs[] = '/' . $tag;
148
			}
149
150
			// attr가 없어야만 되는 것들
151
			if (in_array(strtolower(trim($tag_and_attr_string)), $tags_without_attrs)) {
152
				$replace = trim($tag_and_attr_string);
153
			}
154
155
			// attr가 필수적인것들
156
			$is_matched = !!preg_match("/^(" . implode('|', $tags_with_attrs) .")\s+(.+)$/is", trim($tag_and_attr_string), $sub_matches);
157
			if ($is_matched) {
158
				// 태그명
159
				$tag_name = $sub_matches[1];
160
161
				// attr 문자열 필터링
162
				$attribute_string = $sub_matches[2];
163
				$attributes = self::extractTagAttributes($attribute_string);
164
				$filtered_attributes = self::filterAllowableTagAttributes($tag_name, $attributes);
165
				$replace_attribute_string = self::implodeTagAttributes($filtered_attributes);
166
				if (!StringUtils::isEmpty($replace_attribute_string)) {
167
					$replace = $tag_name . ' ' . $replace_attribute_string;
168
169
					if (in_array(strtolower($tag_name), self::$tags_open_and_close_in_one)) {
170
						$replace .= '/';
171
					}
172
				}
173
			}
174
175
			if (!StringUtils::isEmpty($replace)) {
176
				$replace = '<' . $replace . '>';
177
178
				// 문자열 새로 조합
179
				$original_string_offset = $matches[0][$index][1];
180
				$pre_string = substr($string, 0, $offset + $original_string_offset);
181
				$post_string = substr($string, $offset + $original_string_offset + strlen($original), strlen($string) - ($offset + $original_string_offset + strlen($original)));
182
				$string = $pre_string . $replace . $post_string;
183
184
				// 원본 문자열에서 구한 offset과 변환된 문자열의 offset과 달라지는 점을 고려
185
				$offset += strlen($replace) - strlen($original);
186
			}
187
		}
188
189
		return $string;
190
	}
191
192
	private static function extractTagAttributes($string)
193
	{
194
		$html_attributes = array();
195
		preg_match_all("/([^=\s]+)\s*=\s*[\"']*((?:.(?![\"']*\s+(?:\S+)=|[>\"']))+.)[\"']*/is", htmlspecialchars_decode(stripslashes($string)), $matches, PREG_SET_ORDER);
196
		foreach ($matches as $match) {
0 ignored issues
show
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
197
			$attr = $match[1];
198
			$value = $match[2];
199
			$html_attributes[$attr] = $value;
200
		}
201
202
		return $html_attributes;
203
	}
204
205
	private static function filterAllowableTagAttributes($tag_name, $html_attributes)
206
	{
207
		// 문자열비교시 대소문자를 가리기때문에, 소문자로 해당값 입력
208
		$allowable_attributes = array(
209
			'a' => array('href', 'target'),
210
			'font' => array('color'),
211
			'img' => array('src'),
212
			'video' => array('src')
213
		);
214
215
		$filtered_html_attributes = array();
216
		foreach ($html_attributes as $attr => $value) {
217
			if (!in_array(strtolower($attr), (array)$allowable_attributes[strtolower($tag_name)])) {
218
				continue;
219
			}
220
221
			$filtered_html_attributes[$attr] = $value;
222
		}
223
224
		return $filtered_html_attributes;
225
	}
226
227
	private static function implodeTagAttributes($html_attributes)
228
	{
229
		$attr_values = array();
230
		foreach ($html_attributes as $attr => $value) {
231
			$attr_values[] = $attr . '="' . $value . '"';
232
		}
233
234
		return implode(' ', $attr_values);
235
	}
236
237
	/**
238
	 * 허용하지 않는 태그용 문자를 제거해준다.
239
	 * @param $string
240
	 * @param array $allowable_tags
241
	 * $allowable_tags 배열 형식:
242
	 * $allowable_tags = [
243
	 * 		'tag' => ['attr1', 'attr2']
244
	 * ]
245
	 * @return string
246
	 */
247
	public static function stripNonAllowableTags($string, $allowable_tags)
248
	{
249
		$string = self::filterNonAllowableTags($string, $allowable_tags);
250
		$string = StringUtils::swapTwoSubStrings($string, '<', '&lt;');
251
		$string = StringUtils::swapTwoSubStrings($string, '>', '&gt;');
252
		$string = strip_tags($string);
253
		$string = StringUtils::swapTwoSubStrings($string, '<', '&lt;');
254
		$string = StringUtils::swapTwoSubStrings($string, '>', '&gt;');
255
256
		return $string;
257
	}
258
}
259