| 1 | |
|---|
| 2 | Remarks: |
|---|
| 3 | |
|---|
| 4 | * Each bug is only reported the first time it has been seen, though it may |
|---|
| 5 | occur multiple times. |
|---|
| 6 | |
|---|
| 7 | * Many of the bugs and minor issues reported are merely defensive coding |
|---|
| 8 | practices and may therefor seem overly pedantic. But remember, even |
|---|
| 9 | though code may not be exploitable now does not mean it won't become so |
|---|
| 10 | in the future. Defensive code minimizes uncaught and future security |
|---|
| 11 | problems. |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | Global |
|---|
| 15 | |
|---|
| 16 | Bug: |
|---|
| 17 | |
|---|
| 18 | Check for usage of magic_quotes_gpc(). If this setting is on, all |
|---|
| 19 | incoming data should be stripped of slash-escaped quotes. MvBlog already |
|---|
| 20 | escapes dangerous characters itself and if magic_quotes_gpc() is turned |
|---|
| 21 | on, quotes will be escaped twice. |
|---|
| 22 | |
|---|
| 23 | Bug: |
|---|
| 24 | |
|---|
| 25 | Users can impersonate other users when commenting on a story. There |
|---|
| 26 | does not seem to be a way to prevent people from impersonating |
|---|
| 27 | a user. A possible remedy for this would be to allow users to |
|---|
| 28 | create an account. |
|---|
| 29 | |
|---|
| 30 | Bug: |
|---|
| 31 | |
|---|
| 32 | Use of sprintf() for query building might cause unintended behaviour. |
|---|
| 33 | All %d replacements whose parameter is not an integer will be converted |
|---|
| 34 | to '0'. This will in many cases not be a problem, but in some cases |
|---|
| 35 | it could be. Perhaps parameters should be checked for is_int() and |
|---|
| 36 | > 0 to prevent bogus entries from being entered into the database |
|---|
| 37 | > (even though these probably do not cause any |
|---|
| 38 | security risks) |
|---|
| 39 | |
|---|
| 40 | Security Enhancement: |
|---|
| 41 | |
|---|
| 42 | Defined states. MvBlog has many places where states are undefined. |
|---|
| 43 | Undefined states in a program are a cause of possible (but unseen) |
|---|
| 44 | security problems and bugs. An undefined state is an atomic piece |
|---|
| 45 | of code (object, method, function, include) which recieves input about |
|---|
| 46 | which no assumptions can be made. For instance: |
|---|
| 47 | |
|---|
| 48 | function show_posts($options = array()) { |
|---|
| 49 | global $db; |
|---|
| 50 | if (!$options["top"]) { $options["top"] = 0; } else { $options["top"] = (int)$options["top"]; } |
|---|
| 51 | if (!$options["limit"]) { $options["limit"] = 15; } else { $options["limit"] = (int)$options["limit"]; } |
|---|
| 52 | //put all categories in array |
|---|
| 53 | $res =& $db->query("SELECT * FROM categories"); |
|---|
| 54 | if (PEAR::isError($res)) { |
|---|
| 55 | die($res->getMessage()); |
|---|
| 56 | } |
|---|
| 57 | while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) { |
|---|
| 58 | $cats[$row["id"]] = $row["name"]; |
|---|
| 59 | } |
|---|
| 60 | $cats[-1] = "asides"; |
|---|
| 61 | if ($options["month"] && $options["year"]) { |
|---|
| 62 | |
|---|
| 63 | In the above code (taken from admin/index.php), the following variables |
|---|
| 64 | are in an undefined state: |
|---|
| 65 | |
|---|
| 66 | $db |
|---|
| 67 | global variables tend to be problematic. What kind of variable |
|---|
| 68 | is $db? From inside the function, no assumption can (and |
|---|
| 69 | should) be made about $db being a database connection, making |
|---|
| 70 | security auditing problematic. |
|---|
| 71 | |
|---|
| 72 | $options[] |
|---|
| 73 | |
|---|
| 74 | No assumptions can be made about the contents of $options. |
|---|
| 75 | |
|---|
| 76 | Security enhancement: |
|---|
| 77 | |
|---|
| 78 | MvBlog uses exclusion instead of inclusion. For instance, variables |
|---|
| 79 | that will go into the database are stripped of quotes (') because |
|---|
| 80 | they may give problems but all other characters are considered valid. |
|---|
| 81 | |
|---|
| 82 | Good security practice requires code to use inclusion by default. For |
|---|
| 83 | instance, a value, lets say an authors name, which will go into the |
|---|
| 84 | database should be checked against characters which may only appear in |
|---|
| 85 | the value. In the case of an author's name, only a through z and |
|---|
| 86 | possibly a space should be allowed. |
|---|
| 87 | |
|---|
| 88 | Many security problems can thus be contained. Lets look at two possible |
|---|
| 89 | examples: |
|---|
| 90 | |
|---|
| 91 | preg_quote()ing for "'" does not prevent users from entering percentage |
|---|
| 92 | signs into queries. Thus the programmer has to make sure that for every |
|---|
| 93 | query that uses the LIKE syntax not only the quotes are stripped, but |
|---|
| 94 | also all special characters which can be used in the LIKE syntax. |
|---|
| 95 | |
|---|
| 96 | Another example is that of character sets. Does preg_quote() work with |
|---|
| 97 | different character sets? Suppose our database is of the charset UTF8, |
|---|
| 98 | but PHP is of the charset ISO8859. Will PHP strip out UTF8 version |
|---|
| 99 | of quotes? This behaviour is very hard to determine and can't be |
|---|
| 100 | counted on. It all depends on how PHP _and_ MySQL convert characters |
|---|
| 101 | from one character set to another. If only certain characters where |
|---|
| 102 | allowed though, the chances of the above happening would be practically |
|---|
| 103 | zero. If any other character could be used to perform SQL injections, |
|---|
| 104 | they would never get through because the code only allows characters in |
|---|
| 105 | the, say, A-Z range through and nothing else. |
|---|
| 106 | |
|---|
| 107 | xinha/ |
|---|
| 108 | |
|---|
| 109 | Security enhancement: |
|---|
| 110 | |
|---|
| 111 | Remove examples/ directory. |
|---|
| 112 | |
|---|
| 113 | Rationale: |
|---|
| 114 | Common security vulnerabilities are related to unsecure example |
|---|
| 115 | scripts left in default installs. |
|---|
| 116 | |
|---|
| 117 | /INSTALL |
|---|
| 118 | |
|---|
| 119 | Security enhancement: |
|---|
| 120 | |
|---|
| 121 | Default admin username and password is possibly bad practice. Disable |
|---|
| 122 | mvblog admin until username and password has been manually configured. |
|---|
| 123 | If this will not be fixed, mention it also in chapter 8 of the README. |
|---|
| 124 | |
|---|
| 125 | Rationale: |
|---|
| 126 | MvBlog is vulnerable to an account take-over for a short while |
|---|
| 127 | between installation and configuration. Many users are too lazy to |
|---|
| 128 | change the default password and must therefor be protected from |
|---|
| 129 | themselves. |
|---|
| 130 | |
|---|
| 131 | /index.php |
|---|
| 132 | |
|---|
| 133 | Bug: |
|---|
| 134 | |
|---|
| 135 | Unclosed div (if_page) |
|---|
| 136 | |
|---|
| 137 | /sql/mvblog.sql |
|---|
| 138 | |
|---|
| 139 | Minor issue: |
|---|
| 140 | |
|---|
| 141 | 'ip' field in the 'comments' table is unnecessarily large (255 chars) |
|---|
| 142 | |
|---|
| 143 | Minor issue: |
|---|
| 144 | |
|---|
| 145 | 'website' field in the 'authors' table is not large enough. Though no |
|---|
| 146 | official URL size is specified in any RFC, the minimum supported by all |
|---|
| 147 | browsers is 2048. |
|---|
| 148 | |
|---|
| 149 | Minor issue: |
|---|
| 150 | |
|---|
| 151 | 'tb_uri' field in the 'articles' table is not large enough. See above. |
|---|
| 152 | |
|---|
| 153 | |
|---|
| 154 | /site_images/empty.txt |
|---|
| 155 | |
|---|
| 156 | Minor issue: |
|---|
| 157 | |
|---|
| 158 | 'empty.txt' should not appear in public releases. |
|---|
| 159 | |
|---|
| 160 | /debian/ |
|---|
| 161 | |
|---|
| 162 | Minor issue: |
|---|
| 163 | |
|---|
| 164 | 'debian' directory should not appear in public releases. |
|---|
| 165 | |
|---|
| 166 | |
|---|
| 167 | common/functions_blog.php |
|---|
| 168 | |
|---|
| 169 | Security enhancement: |
|---|
| 170 | |
|---|
| 171 | line 50: if (!$_SESSION["author_id"]) { |
|---|
| 172 | |
|---|
| 173 | Reliance on boolean value of non-boolean variable. This piece of code |
|---|
| 174 | tries to check for the availability of the 'author_id' key in the |
|---|
| 175 | $_SESSION array, but in reality it checks if the value of the |
|---|
| 176 | 'author_id' key in the $_SESSION array is false. Use |
|---|
| 177 | array_key_exists() instead. This problem occurs a multitude of times |
|---|
| 178 | throughout the code. |
|---|
| 179 | |
|---|
| 180 | Rationale: |
|---|
| 181 | |
|---|
| 182 | Undefined or hard to define behaviour in software is a lurking |
|---|
| 183 | place for bugs and security problems. If an attacker were to be |
|---|
| 184 | able to get the value of 'false' into the "author_id" index, the |
|---|
| 185 | check would result in true and the code would be run, while that is |
|---|
| 186 | not the intended behaviour. In this case that would hardly be a |
|---|
| 187 | problem, but code changes and things that are currently not a |
|---|
| 188 | problem may change into one later. |
|---|
| 189 | |
|---|
| 190 | BTW: How does this function actually verify that the administrator |
|---|
| 191 | is logged in and not just any other user? I assume there is no other |
|---|
| 192 | user than the admin? |
|---|
| 193 | |
|---|
| 194 | Minor issue: |
|---|
| 195 | |
|---|
| 196 | line 165: <?="http://".$_SERVER["SERVER_NAME"].substr($_SERVER["PHP_SELF"],0,strrpos($_SERVER["PHP_SELF"], "/"))."/common/tb.php?id=".$row["id"]?> |
|---|
| 197 | |
|---|
| 198 | Code does not work when using HTTPS |
|---|
| 199 | |
|---|
| 200 | Minor issue: |
|---|
| 201 | |
|---|
| 202 | line 192: echo nl2br(stripslashes(htmlspecialchars(strip_invalid_xml($row["comment"])))); |
|---|
| 203 | |
|---|
| 204 | Possible reversed order of stripslashes, htmlspecialchars, |
|---|
| 205 | strip_invalid_xml?? When inserting information into the database, |
|---|
| 206 | stripslashes() is called right before inserting the information. It |
|---|
| 207 | should then also be the very first thing that is called right after |
|---|
| 208 | retrieving information from the database, to prevent undefined |
|---|
| 209 | behaviour. |
|---|
| 210 | |
|---|
| 211 | Security enhancement: |
|---|
| 212 | |
|---|
| 213 | line 320: exec("ls style", $dirindex); |
|---|
| 214 | |
|---|
| 215 | Unnecessary use of exec() function. Use opendir() instead. |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | Security enhancement: |
|---|
| 219 | |
|---|
| 220 | line 569: $q = "FROM articles WHERE active=1 AND public=1 AND aside = 0 AND date BETWEEN $time_start AND $time_end"; |
|---|
| 221 | |
|---|
| 222 | Minimize query injectability. Move 'FROM articles WHERE active=1' to |
|---|
| 223 | the non-injectable code on line 587, 593 and 595. |
|---|
| 224 | |
|---|
| 225 | |
|---|
| 226 | Rationale: |
|---|
| 227 | |
|---|
| 228 | $q might accidentally become an injectable variable in the future. |
|---|
| 229 | By moving the 'FROM articles' portion to the actual query (making |
|---|
| 230 | that portion unmuttable), an attack only has access to the |
|---|
| 231 | 'articles' table instead of all tables. |
|---|
| 232 | |
|---|
| 233 | Minor issue: |
|---|
| 234 | |
|---|
| 235 | line 580: case 0; |
|---|
| 236 | |
|---|
| 237 | Pointless switch case. Remove. |
|---|
| 238 | |
|---|
| 239 | Rationale: |
|---|
| 240 | |
|---|
| 241 | The 'default' switch case already handles the 0 case. In the |
|---|
| 242 | current code '0' is a seperate case which trickels down into the |
|---|
| 243 | default case. If, in the future, someone accidentally puts a |
|---|
| 244 | 'break' in the 0 case or the 0 case is moved, the $q variable might |
|---|
| 245 | become unset or injectable causing query errors and possibly |
|---|
| 246 | SQL injections. |
|---|
| 247 | |
|---|
| 248 | Bug: |
|---|
| 249 | |
|---|
| 250 | line 593: $res =& $db->limitQuery("SELECT * $q ORDER BY date DESC", $start, $limit); |
|---|
| 251 | |
|---|
| 252 | DB::limitQuery does not specify that it converts parameters to ints. |
|---|
| 253 | Cast to int when calling to avoid possible SQL injection. |
|---|
| 254 | |
|---|
| 255 | /rss.php |
|---|
| 256 | |
|---|
| 257 | Bug: |
|---|
| 258 | |
|---|
| 259 | line 87: echo "\t\t\t<author>".$authors[$row["authors_id"]]["email"]." (".$authors[$row["authors_id"]]["fullname"].")</author>\n"; |
|---|
| 260 | |
|---|
| 261 | $authors[$row["authors_id"]]["email"] is not striped of HTML/XML chars |
|---|
| 262 | which may cause invalid XML code in the RSS feed. |
|---|
| 263 | |
|---|
| 264 | |
|---|
| 265 | admin/ |
|---|
| 266 | |
|---|
| 267 | Feature enhancement: |
|---|
| 268 | |
|---|
| 269 | Detection for disabled cookies. |
|---|
| 270 | |
|---|
| 271 | Feature enhancement: |
|---|
| 272 | |
|---|
| 273 | Error message on wrong login. |
|---|