└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # PHP Security Cheatsheet 2 | This cheatsheet is an overview of techniques to prevent common vulnerabilities within PHP web applications. 3 | 4 | > All of the examples presented in this cheatsheet are for learning and experimentation purposes and are not meant to be used in a production system. Most of the techniques and countermeasures are already built-in in many modern web application frameworks and should be taken advantage of. 5 | 6 | ## Articles, Tutorials, Guides and Cheatsheets 7 | In case you are keen on learning more about PHP security, you can check out the following resources: 8 | - [The 2018 Guide to Building Secure PHP Software](https://paragonie.com/blog/2017/12/2018-guide-building-secure-php-software) 9 | - [PHP: The Right Way - Security](https://phptherightway.com/#security) 10 | - [Survive The Deep End: PHP Security](https://phpsecurity.readthedocs.io/en/latest/) 11 | - [PHP Configuration Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html) 12 | - [Awesome PHP Security](https://github.com/guardrailsio/awesome-php-security) 13 | - [PHP RFC: Is Literal Check](https://github.com/craigfrancis/php-is-literal-rfc) 14 | 15 | ## Table of Vulnerabilities 16 | - [Cross-Site Request Forgery](#cross-site-request-forgery) 17 | - [Cross-Site Scripting](#cross-site-scripting) 18 | - [Directory Traversal](#file-inclusion) 19 | - [File Inclusion](#file-inclusion) 20 | - [HTTP Header Injection](#http-header-injection) 21 | - [HTTP Header Parameter Injection](#http-header-parameter-injection) 22 | - [HTTP Response Splitting](#http-header-injection) 23 | - [Information Disclosure](#information-disclosure) 24 | - [Insecure Password Storage and Hashing](#insecure-password-storage-and-hashing) 25 | - [Insecure Random Values](#insecure-random-values) 26 | - [SQL Injection](#sql-injection) 27 | - [Template Injection](#template-injection) 28 | - [UI Redressing](#ui-redressing) 29 | - [Using Packages With Known Vulnerabilities](#using-packages-with-known-vulnerabilities) 30 | 31 | # Cross-Site Request Forgery 32 | > Before going into any of the following countermeasures, it is important to know the concept of [safe HTTP methods](https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP). A HTTP method is considered safe if it is not changing any state on the server-side of a web application or service. HTTP methods such as GET should therefore not be used to, e.g., remove a resource on the server-side. Otherwhise, this would make it possible, if no CSRF countermeasures are in place, to lure a victim to a attacker-controlled website which sends a HTTP GET request (e.g. `GET /delete/:id`) when the website is loaded - and removes a resource using the victim's session. 33 | 34 | > If a HTTP request contains a custom header, the Browser will send a [CORS preflight request](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests) before it continues to send the original request. If no CORS policy has been set on the server, requests coming from another origin will fail. You can enforce this situation by checking for the existence of a custom HTTP request header (e.g. `X-CSRF-Token`) in the list of headers returned by [apache_request_headers](https://secure.php.net/manual/en/function.apache-request-headers.php). However, being able to set arbitrary request headers might still be possible due to vulnerabilities such as ([CVE-2017-0140](https://www.securify.nl/advisory/SFY20170101/microsoft-edge-fetch-api-allows-setting-of-arbitrary-request-headers.html)). 35 | 36 | ### Anti-CSRF Tokens 37 | You can use the [random_bytes](https://secure.php.net/manual/en/function.random-bytes.php) function to generate a cryptographically secure pseudo-random token. The following example describes a basic proof of concept in 38 | which a Anti-CSRF token is delivered to the client in a custom HTTP response header (`X-CSRF-Token`). The [bin2hex](https://secure.php.net/manual/en/function.bin2hex.php) function will be used in order to 39 | prevent issues with the character representation of non-character bytes returned by `random_bytes`. 40 | 41 | ```php 42 | session_start(); 43 | 44 | $tokenLength = 64; 45 | 46 | $_SESSION["CSRF_TOKEN"] = bin2hex(random_bytes($tokenLength)); 47 | 48 | header("X-CSRF-Token: " . $_SESSION["CSRF_TOKEN"]); 49 | 50 | // ... 51 | ``` 52 | 53 | Instead of simply comparing two values and their data types with `===`, the [hash_equals](https://secure.php.net/manual/en/function.hash-equals.php) function is used to prevent timing attacks against string comparisons. Have a look at this article on [timing attacks](https://blog.ircmaxell.com/2014/11/its-all-about-time.html) for further details. 54 | 55 | ```php 56 | $serverToken = $_SESSION["CSRF_TOKEN"]; 57 | $requestHeaders = apache_request_headers(); 58 | 59 | if($requestHeaders !== false && 60 | array_key_exists("X-CSRF-Token", $requestHeaders)){ 61 | 62 | $clientToken = $requestHeaders["X-CSRF-Token"]; 63 | 64 | if(hash_equals($serverToken, $clientToken)){ 65 | // Move on with request processing 66 | } 67 | else { 68 | // Do not continue with request processing 69 | exit; 70 | } 71 | } 72 | else { 73 | // Do not continue with request processing 74 | exit; 75 | } 76 | ``` 77 | 78 | ### SameSite Cookie Attribute 79 | The support of the SameSite cookie attribute was introduced in [PHP 7.3](https://wiki.php.net/rfc/same-site-cookie). 80 | 81 | ```php 82 | bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, 83 | string $domain = "" [, bool $secure = false [, bool $httponly = false [, string $samesite = "" 84 | ]]]]]]] ) 85 | ``` 86 | 87 | > The SameSite cookie attribute won't prevent request forgery attacks that occur on-site ([OSRF](https://portswigger.net/blog/on-site-request-forgery)). However, this type of request forgery is not so common and can be prevented with Anti-CSRF tokens as well. 88 | 89 | # Cross-Site Scripting 90 | > Server-side countermeasures will not be enough to prevent XSS attacks as certain types of XSS, such as DOM-based XSS, 91 | > are the results of flaws in the client-side code. In case of DOM-based XSS, I recommend to use [DOMPurify](https://github.com/cure53/DOMPurify) and 92 | > to take a look at the [DOM-based XSS Prevention Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html). Furthermore, you should also follow the development of [Trusted Types for DOM Manipulation](https://github.com/WICG/trusted-types) - See [ 93 | Trusted Types help prevent Cross-Site Scripting](https://developers.google.com/web/updates/2019/02/trusted-types) for a recent article on that topic. 94 | 95 | ### Automatic Context-Aware Escaping 96 | Automatic context-aware escaping should be your main line of defense against XSS attacks. Personally, I recommend using the [Latte](https://latte.nette.org/en/guide#toc-context-aware-escaping) template engine as it covers various contexts such as HTML element, HTML attribute and the href attribute of an anchor element. 97 | 98 | ### Manual Context-Aware Escaping 99 | ###### Context: Inside a HTML element and HTML element attribute 100 | [htmlentities](https://secure.php.net/manual/en/function.htmlentities.php) encodes all characters which have a reference in a specified HTML entity set. 101 | 102 | ```php 103 | $escapedString = htmlentities("", ENT_QUOTES | ENT_HTML5, "UTF-8", true); 104 | ``` 105 | 106 | The `ENT_QUOTES` flag makes sure that both single and double quotes will be encoded since the default flag does not encode single quotes. The `ENT_HTML5` flag encodes characters to their referenced entities in the [HTML5 entity set](https://dev.w3.org/html5/html-author/charref). Using the HTML5 entity set has the advantage that most of the special characters will be encoded as well in comparsion to the entity set defined by the default flag (`ENT_HTML401`). 107 | 108 | Special Characters: 109 | ``` 110 | +-#~_.,;:@€<§%&/()=?*'"°^[]{}\`´=<,|²³ 111 | ``` 112 | 113 | Encoded with `ENT_HTML401` Flag: 114 | ``` 115 | +-#~_.,;:@€<§%&/()=?*'"°^[]{}\`´=<,|²³ 116 | ``` 117 | 118 | Encoded with `ENT_HTML5` Flag: 119 | ``` 120 | +-#~_.,;:@€<§%&/ 121 | ()=?*'"°^[]{}\` 122 | ´=<,|²³ 123 | ``` 124 | 125 | The default flag won't protect you sufficiently if you forget to enclose your HTML attributes in **single quotes** 126 | or **double quotes**. For example, the `htmlentities` function won't encode the characters of the following XSS 127 | payload: 128 | 129 | ``` 130 | 1 onmouseover=alert(1) 131 | ``` 132 | 133 | This payload can be used in a situation like the following: 134 | 135 | ```html 136 |
137 | ``` 138 | 139 | However, with the `ENT_HTML5` flag, the payload would not be usable 140 | in the previously described situation: 141 | 142 | ```html 143 | 144 | ``` 145 | 146 | Regardless of the flag you set, **always** enclose HTML attributes in **single quotes** or **double quotes**. 147 | 148 | With the third parameter of the `htmlentities` function, the target character set is specified. The value of 149 | this parameter should be equal to the character set defined in the target HTML document (e.g. UTF-8). 150 | 151 | Finally, the fourth parameter prevents double escaping if set to true. 152 | 153 | ###### Context: User-provided URLs 154 | User-provided URLs should not beginn with the JavaScript (`javascript:`) or a data (`data:`) URI scheme. This can be prevented by accepting only URLs that beginn with the HTTPS (`https`) protocol. 155 | 156 | ```php 157 | if(substr($url, 0, strlen("https")) === "https"){ 158 | // Accept and process URL 159 | } 160 | ``` 161 | ###### Context: Inside a script element or inline event handler 162 | PHP does not provide a native function to escape user input in a JavaScript context. The following code snippet is from the [Escaper Component](https://github.com/zendframework/zend-escaper/blob/master/src/Escaper.php) of the Zend Framework which implements JavaScript context escaping. 163 | 164 | ```php 165 | /** 166 | * Escape a string for the Javascript context. This does not use json_encode(). An extended 167 | * set of characters are escaped beyond ECMAScript's rules for Javascript literal string 168 | * escaping in order to prevent misinterpretation of Javascript as HTML leading to the 169 | * injection of special characters and entities. The escaping used should be tolerant 170 | * of cases where HTML escaping was not applied on top of Javascript escaping correctly. 171 | * Backslash escaping is not used as it still leaves the escaped character as-is and so 172 | * is not useful in a HTML context. 173 | * 174 | * @param string $string 175 | * @return string 176 | */ 177 | public function escapeJs($string) 178 | { 179 | $string = $this->toUtf8($string); 180 | if ($string === '' || ctype_digit($string)) { 181 | return $string; 182 | } 183 | $result = preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, $string); 184 | return $this->fromUtf8($result); 185 | } 186 | ``` 187 | ```php 188 | /** 189 | * Callback function for preg_replace_callback that applies Javascript 190 | * escaping to all matches. 191 | * 192 | * @param array $matches 193 | * @return string 194 | */ 195 | protected function jsMatcher($matches) 196 | { 197 | $chr = $matches[0]; 198 | if (strlen($chr) == 1) { 199 | return sprintf('\\x%02X', ord($chr)); 200 | } 201 | $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8'); 202 | $hex = strtoupper(bin2hex($chr)); 203 | if (strlen($hex) <= 4) { 204 | return sprintf('\\u%04s', $hex); 205 | } 206 | $highSurrogate = substr($hex, 0, 4); 207 | $lowSurrogate = substr($hex, 4, 4); 208 | return sprintf('\\u%04s\\u%04s', $highSurrogate, $lowSurrogate); 209 | } 210 | ``` 211 | The jsMatcher function escapes each character of the target string that matches the regular expression used in the `escapeJs` function (`[^a-z0-9,\._]/iSu`). The current character will be encoded in hexadecimal if it is not greater than one byte. Note that [strlen](https://secure.php.net/en/strlen) returns the number of bytes and not the number of characters (this is a documented behavior). 212 | 213 | Otherwise, the current character will be encoded in Unicode. Some characters can only be encoded in [UTF-16](https://en.wikipedia.org/wiki/UTF-16), using two 16-bit code units (referred as `$highSurrogate` and `$lowSurrogate` at the end of the `jsMatcher` function). This article on [JavaScript's internal character encoding](https://mathiasbynens.be/notes/javascript-encoding) will help you understand the details why certain characters need to be encoded in UTF-16. 214 | 215 | ### HTTPOnly Cookie Attribute 216 | The HTTPOnly cookie attribute signals the Browser to prevent any client-side scripts from accessing data stored in a cookie. The intention behind this cookie attribute is to protect session identifiers within cookies from XSS attacks with a session hijacking payload. Please note that this cookie attribute does not prevent XSS attacks in general. 217 | 218 | ```php 219 | bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, 220 | string $domain = "" [, bool $secure = false [, bool $httponly = false [, string $samesite = "" 221 | ]]]]]]] ) 222 | ``` 223 | 224 | You can also set the HTTPOnly cookie attribute in your PHP configuration file using the [session.cookie_httponly](https://secure.php.net/manual/en/session.configuration.php#ini.session.cookie-httponly) parameter. 225 | 226 | ``` 227 | session.cookie_httponly = true 228 | ``` 229 | 230 | ### Content Security Policy 231 | Another effective defense against XSS attacks is to utilize a so called [Content Security Policy](https://developers.google.com/web/fundamentals/security/csp) (CSP). Essentially, a CSP is an acceptlist of trusted sources from which a web application (the frontend part) is allowed to download and render/execute content. A CSP could therefore prevent the exfiltration of data (e.g. session ID) to a source that is not in the acceptlist. 232 | 233 | In cases where an attacker cannot exfiltrate data but execute code, a CSP can still be beneficial as it provides mechanisms to prevent the execution of inline JavaScript code (unless `unsafe-inline` is explicitly specified as a trusted source). This, however, presumes an application architecture in which aspects such as the application's behavior and its appearance are separated (e.g. all JavaScript code are contained in .js files, all style instructions are cointained in .css files). If that should not be the case, you might be able to [use nonces to add inlined resources to the acceptlist](https://barryvanveen.nl/blog/47-how-to-prevent-the-use-of-unsafe-inline-in-csp). 234 | 235 | A CSP is delivered to a Browser as a HTTP response header as shown below: 236 | 237 | ```php 238 | // Starter Policy from https://content-security-policy.com/ 239 | header("Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';"); 240 | ``` 241 | 242 | While the CSP in the example above is short and simple, it is not unusual to have a large CSP or a different CSP for specific pages. In such scenarios, it makes sense to make use of libraries such as the [CSP Builder](https://github.com/paragonie/csp-builder) to ease the integration and maintenance of CSPs. 243 | 244 | > A CSP is a mitigation technique against XSS attacks, **it does not fix the vulnerability** through which an XSS attack has 245 | > been executed. For that reason, a CSP should be rather seen as **a defense-in-depth strategy** on top of context-aware 246 | > escaping of user-controlled input, which is far more important. Furthermore, as with all mitigation techniques, CSPs can 247 | > be bypassed with [Script Gadgets](https://github.com/google/security-research-pocs/tree/master/script-gadgets) 248 | > or by exploiting [common CSP mistakes](http://conference.hitb.org/hitbsecconf2016ams/materials/D1T2%20-%20Michele%20Spagnuolo%20and%20Lukas%20Weichselbaum%20-%20CSP%20Oddities.pdf) to name but a few examples. 249 | 250 | # File Inclusion 251 | The user should not have the possibility to control parameters that include files from the local filesystem or from a remote host. If this behavior cannot be changed, apply parameter acceptlisting such that only valid parameters are accepted. This will also prevent attackers from traversing through the local file system. 252 | 253 | ```php 254 | $parameterAcceptlist = ["preview", "gallery"]; 255 | // Activate type checking of the needle-parameter by setting 256 | // the third parameter of the in_array function to true 257 | if(in_array($parameter, $parameterAcceptlist, true)){ 258 | include($parameter . ".php"); 259 | } 260 | ``` 261 | 262 | # HTTP Header Injection 263 | The [header](https://secure.php.net/manual/en/function.header.php) function prevents the injection of multiple headers since PHP 5.1.2 (see [Changelog](https://secure.php.net/manual/en/function.header.php) at the bottom). 264 | 265 | # HTTP Header Parameter Injection 266 | User-provided header parameters should be avoided if possible. If it can't be avoided, consider an acceptlist approach to accept only specific values. The following sample shows how to prevent unvalidated redirection attacks with an acceptlist of valid locations. 267 | 268 | ```php 269 | $parameterAcceptlist = ["ManagementPanel", "Dashboard"]; 270 | // Activate type checking of the needle-parameter by setting 271 | // the third parameter of the in_array function to true 272 | if(in_array($parameter, $parameterAcceptlist, true)){ 273 | header("Location: /" . $parameter, true, 302); 274 | exit; 275 | } 276 | ``` 277 | # Information Disclosure 278 | ### Error Messages 279 | Many attacks against web applications exploit error messages to infer information on how the attack payload needs to be adjusted for a successful attack. Example attack techniques that utilize error messages are [SQL Injection](https://en.wikipedia.org/wiki/SQL_injection) or a [Padding Oracle](https://en.wikipedia.org/wiki/Padding_oracle_attack). For that reason, production systems should never display error messages. Instead, error messages should be logged using a library like [Monolog](https://github.com/Seldaek/monolog). 280 | 281 | PHP provides the [display_errors](https://secure.php.net/manual/en/errorfunc.configuration.php#ini.display-errors) configuration parameter to determine if error messages should be part of the output. Use the value `off` to disable displaying any error messages. 282 | 283 | ``` 284 | display_errors = off 285 | ``` 286 | The same value should be applied for the [display_startup_errors](https://secure.php.net/manual/en/errorfunc.configuration.php#ini.display-startup-errors) configuration parameter which determines whether to display error messages that occur during PHP's startup sequence. 287 | 288 | ``` 289 | display_startup_errors = off 290 | ``` 291 | 292 | > It is also possible to set these configuration parameters at runtime with, e.g., `ini_set("display_errors", "off");`. 293 | > But it is not recommended as any fatal error would stop the execution of a PHP script and thus ignore the line with 294 | > the [ini_set](https://secure.php.net/manual/en/function.ini-set.php) function call. 295 | 296 | Disabling the displaying of error messages should not be the primary defense against attacks like SQL Injection as there are other techniques such as [Blind SQL Injection](https://www.owasp.org/index.php/Blind_SQL_Injection) that do not necessarily rely on error messages. 297 | 298 | ### PHP Exposure 299 | The following countermeasures are meant to hide the fact that your web application is built in PHP. Be aware that hiding this fact won't make existing vulnerabilities in your web application go away. It is rather meant as a countermeasure against the reconnaissance process of an attacker, where an attacker attempts to learn as much about a target system as possible. 300 | 301 | Obviously, the techniques in this section won't be of much use if functionalities of a web application are accessed by requests such as `/showImage.php?id=23` where the file extension exposes the technology in use. However, you can hide the file extension on the fly with [mod_rewrite](https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html) if you are serving your web application with the Apache web server. 302 | 303 | ###### Rename PHP Session Name 304 | The default session name is `PHPSESSID`, you can change this name by setting the [session.name](https://secure.php.net/manual/en/session.configuration.php#ini.session.name) parameter in your PHP configuration file. 305 | 306 | ``` 307 | session.name = "SESSION_IDENTITY" 308 | ``` 309 | 310 | ###### Disable X-Powered-By Header 311 | Setting the [expose_php](https://secure.php.net/manual/en/ini.core.php#ini.expose-php) parameter to `off` in your PHP configuration file will removed the X-Powered-By Header from any HTTP Response. 312 | 313 | ``` 314 | expose_php = off 315 | ``` 316 | # Insecure Password Storage and Hashing 317 | It should be needless to say that passwords **should never be stored in clear text**. The best practice is to store the hash value of the password instead. PHP provides a built-in function for this purpose which is called [password_hash](https://www.php.net/manual/en/function.password-hash.php). 318 | 319 | ```php 320 | $clearTextPassword = $_POST["Password"]; 321 | $passwordHash = password_hash($clearTextPassword, PASSWORD_DEFAULT); 322 | ``` 323 | 324 | You can use the built-in [password_verify](https://www.php.net/manual/en/function.password-verify.php) function to verify a user-provided password. The `password_verify` function will also require the hash value that you stored and generated with the `password_hash`function. 325 | 326 | ```php 327 | $clearTextPassword = $_POST["Password"]; 328 | if(password_verify($clearTextPassword, $passwordHash)){ 329 | // Password is correct 330 | } 331 | ``` 332 | 333 | # Insecure Random Values 334 | ### Pseudo-Random Bytes 335 | The [random_bytes](https://secure.php.net/manual/en/function.random-bytes.php) function generates an arbitrary length string of pseudo-random bytes which are secure for cryptographic use. 336 | 337 | ```php 338 | string random_bytes ( int $length ) 339 | ``` 340 | 341 | ### Pseudo-Random Integers 342 | The [random_int](https://secure.php.net/manual/en/function.random-int.php) function generates a pseudo-random integer which is secure for cryptographic use. 343 | 344 | ```php 345 | int random_int ( int $min , int $max ) 346 | ``` 347 | 348 | # SQL Injection 349 | This type of vulnerability affects applications that interact with a SQL database for data storage and processing. The vulnerability occurs when a SQL query is dynamically constructed with user-controlled input and the user-controlled input is neither sanitized nor escaped. The best practice to prevent SQL injection vulnerabilities is to process user-controlled input and the SQL query separately and this can be done by using prepared statements. The [PDO](https://www.php.net/manual/en/book.pdo.php) database abstraction layer in PHP enables prepared statements through the [prepare](https://www.php.net/manual/en/pdo.prepare.php) method of the [PDO](https://www.php.net/manual/en/class.pdo.php) class. 350 | 351 | ```php 352 | // Init and connect to database / Instantiate a PDO object 353 | // ... 354 | 355 | // Read user credentials 356 | $eMail = $_POST["Email"]; 357 | $passwordHash = password_hash($_POST["Password"], PASSWORD_DEFAULT); 358 | 359 | // Read user record from database based on the provided user credentials 360 | $statement = $pdo->prepare("SELECT * FROM Users WHERE Email = :eMail AND PasswordHash=:passwordHash"); 361 | $statement->execute(["eMail" => $eMail, "passwordHash" => $passwordHash]); 362 | $user = $statement->fetch(); 363 | ``` 364 | > Note that the SQL query in the previous example is constructed as a string because that is what the PDO prepare method expects. This way of constructing SQL queries should rather be the exception as it is prone to developer mistakes who could accidentally embed user-controlled input into the string. Object-Relational Mapper (ORM) like [Doctrine](https://www.doctrine-project.org/) can make such mistakes less likely and provide more usable interfaces to construct queries in a object-oriented manner. 365 | 366 | 367 | # Template Injection 368 | This type of vulnerability occurs when the target template is built at runtime and parts of the template are controlled by the user. Template engines provide functions to safely embed user-controlled input into a template, make use of them. The following code snippet shows an example where user input is safely embed in a [Smarty](https://www.smarty.net/) template. 369 | 370 | ```php 371 | // Replacing {$searchTerm} with $_GET["searchTerm"] in the next line 372 | // would introduce a template injection vulnerability 373 | $templateString = "You searched for: {$searchTerm}"; 374 | 375 | $smarty = new Smarty(); 376 | $smarty->assign("searchTerm", $_GET["searchTerm"]); 377 | $smarty->display("string:" . $templateString); 378 | ``` 379 | 380 | If you want to learn more on template injection vulnerabilities and how they can lead to remote code execution, watch this talk on [server-side template injection](https://www.youtube.com/watch?v=3cT0uE7Y87s). 381 | 382 | > Template injection is not limited to server-side web technologies and can also occur on the client-side. 383 | > Have a look at this talk on [client-side template injection](https://www.youtube.com/watch?v=VDAAGm_HUQU). 384 | 385 | # UI Redressing 386 | To prevent UI redressing attacks such as Clickjacking, prohibit a malicious website from embedding your website in a frame by using the [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) header. 387 | 388 | ```php 389 | header("X-Frame-Options: deny"); 390 | ``` 391 | # Using Packages With Known Vulnerabilities 392 | When you integrate third party packages in your application, typically via a package manager like [Composer](https://getcomposer.org/), you might not be aware of packages containing exploitable vulnerabilities. Apart 393 | from staying up to date on vulnerabilities affecting the packages you use, you can also make use of security 394 | packages like the one from [Roave](https://github.com/Roave/SecurityAdvisories) which prevents you from 395 | installing known vulnerable packages in the first place. Roaves source for vulnerable PHP packages is the 396 | [PHP Security Advisories Database](https://github.com/FriendsOfPHP/security-advisories). 397 | 398 | > Have a look at [A9 - Using Components with Known Vulnerabilities](https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A9-Using_Components_with_Known_Vulnerabilities.html) from the [OWASP Top 10](https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/) project for further guidance. 399 | --------------------------------------------------------------------------------