|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang=en> |
| 3 | +<head> |
| 4 | + |
| 5 | + <meta charset="utf-8"> |
| 6 | + <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| 7 | + <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 8 | + <meta property="og:type" content="website"> |
| 9 | + <meta name="author" content="Yaniv Nizry"> |
| 10 | + <meta name="description" content="Security research blog"> |
| 11 | + <meta name="keyword" content="security,research,vulnerability,blog,vulnerabilites"> |
| 12 | + <link rel="shortcut icon" href="/img/favicon.ico"> |
| 13 | + <title> |
| 14 | + |
| 15 | + Apache httpd XSS Using Multiple Extensions - YNizry |
| 16 | + |
| 17 | + </title> |
| 18 | + |
| 19 | + <!-- Custom CSS --> |
| 20 | + |
| 21 | +<link rel="stylesheet" href="/css/aircloud.css"> |
| 22 | + |
| 23 | + |
| 24 | +<link rel="stylesheet" href="/css/gitment.css"> |
| 25 | + |
| 26 | + <!--<link rel="stylesheet" href="https://imsun.github.io/gitment/style/default.css">--> |
| 27 | + <link href="//at.alicdn.com/t/font_620856_28hi1hpxx24.css" rel="stylesheet" type="text/css"> |
| 28 | + |
| 29 | + <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> |
| 30 | + <style> |
| 31 | + .googleicon { |
| 32 | + vertical-align: middle; |
| 33 | + font-size: 20px; |
| 34 | + font-style: normal; |
| 35 | + -webkit-font-smoothing: antialiased; |
| 36 | + -moz-osx-font-smoothing: grayscale; |
| 37 | + color: #999999; |
| 38 | + } |
| 39 | + </style> |
| 40 | + |
| 41 | + <!-- ga & ba script hoook --> |
| 42 | + <script></script> |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | + |
| 47 | + <script async src="https://www.googletagmanager.com/gtag/js?id=G-SCK9223GY8"></script> |
| 48 | + <script> |
| 49 | + window.dataLayer = window.dataLayer || [] |
| 50 | + function gtag() { |
| 51 | + dataLayer.push(arguments) |
| 52 | + } |
| 53 | + gtag('js', new Date()) |
| 54 | + gtag('config', 'G-SCK9223GY8') |
| 55 | + </script> |
| 56 | + |
| 57 | + |
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +<meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="Yaniv Nizry Blogs" type="application/atom+xml"> |
| 68 | +</head> |
| 69 | + |
| 70 | +<body> |
| 71 | + |
| 72 | +<div class="site-nav-toggle" id="site-nav-toggle"> |
| 73 | + <button> |
| 74 | + <span class="btn-bar"></span> |
| 75 | + <span class="btn-bar"></span> |
| 76 | + <span class="btn-bar"></span> |
| 77 | + </button> |
| 78 | +</div> |
| 79 | + |
| 80 | +<div class="index-about"> |
| 81 | + <i> </i> |
| 82 | +</div> |
| 83 | + |
| 84 | +<div class="index-container"> |
| 85 | + |
| 86 | + <div class="index-left"> |
| 87 | + |
| 88 | +<div class="nav" id="nav"> |
| 89 | + <div class="avatar-name"> |
| 90 | + <div class="avatar radius"> |
| 91 | + <img src="/img/avatar.webp" /> |
| 92 | + </div> |
| 93 | + <div class="name"> |
| 94 | + <i>Yaniv Nizry</i> |
| 95 | + </div> |
| 96 | + </div> |
| 97 | + <div class="contents" id="nav-content"> |
| 98 | + <ul> |
| 99 | + <li > |
| 100 | + <a href="/"> |
| 101 | + <i class="material-icons googleicon">home</i> |
| 102 | + <span>Blogs</span> |
| 103 | + </a> |
| 104 | + </li> |
| 105 | + <li > |
| 106 | + <a href="/talks"> |
| 107 | + <i class="material-icons googleicon">co_present</i> |
| 108 | + <span>Talks</span> |
| 109 | + </a> |
| 110 | + </li> |
| 111 | + <li > |
| 112 | + <a href="/advisories"> |
| 113 | + <i class="material-icons googleicon">speaker_notes</i> |
| 114 | + <span>Advisories</span> |
| 115 | + </a> |
| 116 | + </li> |
| 117 | + <li > |
| 118 | + <a href="/tags"> |
| 119 | + <i class="material-icons googleicon">label</i> |
| 120 | + <span>Tags</span> |
| 121 | + </a> |
| 122 | + </li> |
| 123 | + <li > |
| 124 | + <a href="/archive"> |
| 125 | + <i class="material-icons googleicon">archive</i> |
| 126 | + <span>Archives</span> |
| 127 | + </a> |
| 128 | + </li> |
| 129 | + <li > |
| 130 | + <a href="/about/"> |
| 131 | + <i class="material-icons googleicon">person</i> |
| 132 | + <span>About</span> |
| 133 | + </a> |
| 134 | + </li> |
| 135 | + |
| 136 | + <li> |
| 137 | + <a id="search"> |
| 138 | + <i class="material-icons googleicon">search</i> |
| 139 | + <span>Search</span> |
| 140 | + </a> |
| 141 | + </li> |
| 142 | + |
| 143 | + </ul> |
| 144 | + </div> |
| 145 | + |
| 146 | + <div id="toc" class="toc-article"> |
| 147 | + <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#Introduction"><span class="toc-text">Introduction</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Details"><span class="toc-text">Details</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#The-Attack-Scenario"><span class="toc-text">The Attack Scenario</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#References"><span class="toc-text">References</span></a></li></ol> |
| 148 | +</div> |
| 149 | + |
| 150 | +</div> |
| 151 | + |
| 152 | + |
| 153 | +<div class="search-field" id="search-field"> |
| 154 | + <div class="search-bg" id="search-bg"></div> |
| 155 | + <div class="search-container"> |
| 156 | + <div class="search-input"> |
| 157 | + <span id="esc-search"> <i class="icon-fanhui iconfont"></i></span> |
| 158 | + <input id="search-input"/> |
| 159 | + <span id="begin-search">search</span> |
| 160 | + </div> |
| 161 | + <div class="search-result-container" id="search-result-container"> |
| 162 | + |
| 163 | + </div> |
| 164 | + </div> |
| 165 | +</div> |
| 166 | + |
| 167 | + <div class="index-about-mobile"> |
| 168 | + <i> </i> |
| 169 | + </div> |
| 170 | + </div> |
| 171 | + |
| 172 | + <div class="index-middle"> |
| 173 | + <!-- Main Content --> |
| 174 | + <div class="post-container"> |
| 175 | + <div class="post-title"> |
| 176 | + Apache httpd XSS Using Multiple Extensions |
| 177 | + </div> |
| 178 | + |
| 179 | + <div class="post-meta"> |
| 180 | + <span class="attr">Post:<span>2025-07-07 22:00:00</span></span> |
| 181 | + |
| 182 | + |
| 183 | + <span class="attr">Tags:/ |
| 184 | + |
| 185 | + <a class="tag" href="/tags/#apache" title="apache">apache</a> |
| 186 | + <span>/</span> |
| 187 | + |
| 188 | + <a class="tag" href="/tags/#content-type" title="content-type">content-type</a> |
| 189 | + <span>/</span> |
| 190 | + |
| 191 | + <a class="tag" href="/tags/#xss" title="xss">xss</a> |
| 192 | + <span>/</span> |
| 193 | + |
| 194 | + </span> |
| 195 | + |
| 196 | + |
| 197 | + |
| 198 | + |
| 199 | + |
| 200 | + |
| 201 | + |
| 202 | + <!--<span class="attr">Visit:<span id="busuanzi_value_page_pv"></span>--> |
| 203 | + </span> |
| 204 | + </span> |
| 205 | + </div> |
| 206 | + |
| 207 | + <div class="post-source-meta post-meta"> |
| 208 | + |
| 209 | + </div> |
| 210 | + <div class="post-content no-indent"> |
| 211 | + <h1 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h1><p>This post dives into a stored Cross-Site Scripting (XSS) technique I discovered while researching Fortinet’s endpoint protection solution. It builds on previous work, specifically <a href="https://yaniv-git.github.io/2025/06/29/Caught%20in%20the%20FortiNet:%20How%20Attackers%20Can%20Exploit%20FortiClient%20to%20Compromise%20Organizations%202/#From-Limited-File-Write-to-XSS-CVE-2025-22859">part two</a> of my series (CVE-2025-22859), where I detailed an <code>httpd</code> stored XSS vulnerability using a files with a predefined extensions.</p> |
| 212 | +<p>I have already <a href="https://yaniv-git.github.io/2023/11/04/Apache%20httpd%20XSS%20by%20design/">covered</a> a small technique of achieving XSS on <code>httpd</code> when the attacker can’t control the file extension. However, unlike that prior method, which involved creating files with no name or only dots to bypass extension assignage, this new trick doesn’t depend on the absense of the <code>X-Content-Type-Options: nosniff</code> header. Complicating the other writeup, making these nice to add to our toolbox.</p> |
| 213 | +<h1 id="Details"><a href="#Details" class="headerlink" title="Details"></a>Details</h1><p>The core of this technique lies in how <a target="_blank" rel="noopener" href="https://httpd.apache.org/">Apache httpd’s</a> <a target="_blank" rel="noopener" href="https://httpd.apache.org/docs/2.4/mod/mod_mime.html">mod_mime</a> module determines a file’s <code>Content-Type</code>. Typically, <code>mod_mime</code> guesses the content type based on the file’s extension. If the <code>X-Content-Type-Options: nosniff</code> header is present, browsers are instructed not to “sniff” the content type and will default to <code>text/plain</code> when an <code>Content-Type</code> isn’t set. However, a closer look at the <code>mod_mime</code> documentation reveals an interesting behavior: </p> |
| 214 | +<img src="/img/blogs/fortinet/2/mod_mime_doc.png" style="width: 100%;"/> |
| 215 | + |
| 216 | +<p>Files can have <a target="_blank" rel="noopener" href="https://httpd.apache.org/docs/2.4/mod/mod_mime.html#multipleext" title="multiple extensions">multiple extensions</a>, with a priority given to the last one. For example, these file extensions will correspond to the following content-types:</p> |
| 217 | +<table> |
| 218 | +<thead> |
| 219 | +<tr> |
| 220 | +<th>File Extension</th> |
| 221 | +<th>mod_mime Content-Type</th> |
| 222 | +</tr> |
| 223 | +</thead> |
| 224 | +<tbody><tr> |
| 225 | +<td>Filename.<strong>html</strong></td> |
| 226 | +<td>text/html</td> |
| 227 | +</tr> |
| 228 | +<tr> |
| 229 | +<td>Filename.<strong>gif</strong></td> |
| 230 | +<td>image/gif</td> |
| 231 | +</tr> |
| 232 | +<tr> |
| 233 | +<td>Filename.gif.<strong>html</strong></td> |
| 234 | +<td>text/html</td> |
| 235 | +</tr> |
| 236 | +<tr> |
| 237 | +<td>Filename.<strong>unknown</strong></td> |
| 238 | +<td></td> |
| 239 | +</tr> |
| 240 | +<tr> |
| 241 | +<td>Filename.unknown.<strong>html</strong></td> |
| 242 | +<td>text/html</td> |
| 243 | +</tr> |
| 244 | +<tr> |
| 245 | +<td>Filename.<strong>html</strong>.unknown</td> |
| 246 | +<td>text/html</td> |
| 247 | +</tr> |
| 248 | +</tbody></table> |
| 249 | +<h1 id="The-Attack-Scenario"><a href="#The-Attack-Scenario" class="headerlink" title="The Attack Scenario"></a>The Attack Scenario</h1><p>Armed with this knowledge, in a scenario where an attacker has the ability to upload a file, but can’t control the extension. If the extension doesn’t correlate to any content type in <code>mod_mime</code> (for example <code>.abc</code>), all the attacker would need to do is to add <code>.html</code> to the filename (<code>filename.html.abc</code>). When Apache serves this file, <code>mod_mime</code> will process the multiple extensions, recognize <code>.html</code> as the last and most significant one since <code>.abc</code> doesnt cererlate ot anything, and serve the file with <code>Content-Type: text/html</code>, resulting in stored XSS. This technique might also be used to bypass some server-side validation that only permits specific file types.</p> |
| 250 | +<h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><ul> |
| 251 | +<li><a href="https://yaniv-git.github.io/2025/06/29/Caught%20in%20the%20FortiNet:%20How%20Attackers%20Can%20Exploit%20FortiClient%20to%20Compromise%20Organizations%202/#From-Limited-File-Write-to-XSS-CVE-2025-22859">CVE-2025-22859 blog</a></li> |
| 252 | +<li><a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options">X-Content-Type-Options</a></li> |
| 253 | +<li><a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type">Content-Type</a></li> |
| 254 | +<li><a target="_blank" rel="noopener" href="https://httpd.apache.org/docs/2.4/mod/mod_mime.html">mod_mime</a></li> |
| 255 | +<li><a target="_blank" rel="noopener" href="https://x.com/YNizry/status/1940053407127007587">Tweet</a></li> |
| 256 | +</ul> |
| 257 | + |
| 258 | + |
| 259 | + <br /> |
| 260 | + <div id="comment-container"> |
| 261 | + </div> |
| 262 | + <div id="disqus_thread"></div> |
| 263 | + <div id="lv-container"></div> |
| 264 | + <div class="giscus"></div> |
| 265 | + </div> |
| 266 | +</div> |
| 267 | + |
| 268 | + </div> |
| 269 | +</div> |
| 270 | + |
| 271 | + |
| 272 | +<footer class="footer"> |
| 273 | + <ul class="list-inline text-center"> |
| 274 | + |
| 275 | + <li> |
| 276 | + <a target="_blank" href="https://twitter.com/YNizry"> |
| 277 | + <span class="fa-stack fa-lg"> |
| 278 | + <i class="iconfont icon-twitter"></i> |
| 279 | + </span> |
| 280 | + </a> |
| 281 | + </li> |
| 282 | + |
| 283 | + |
| 284 | + |
| 285 | + |
| 286 | + |
| 287 | + |
| 288 | + |
| 289 | + |
| 290 | + <li> |
| 291 | + <a target="_blank" href="https://github.com/yaniv-git"> |
| 292 | + <span class="fa-stack fa-lg"> |
| 293 | + <i class="iconfont icon-github"></i> |
| 294 | + </span> |
| 295 | + </a> |
| 296 | + </li> |
| 297 | + |
| 298 | + |
| 299 | + |
| 300 | + <li> |
| 301 | + <a target="_blank" href="https://www.linkedin.com/in/yaniv-n-8b4a76193"> |
| 302 | + <span class="fa-stack fa-lg"> |
| 303 | + <i class="iconfont icon-linkedin"></i> |
| 304 | + </span> |
| 305 | + </a> |
| 306 | + </li> |
| 307 | + |
| 308 | + |
| 309 | + </ul> |
| 310 | + |
| 311 | + <p> |
| 312 | + Theme <a target="_blank" rel="noopener" href="https://github.com/aircloud/hexo-theme-aircloud">AirCloud</a></p> |
| 313 | +</footer> |
| 314 | + |
| 315 | + |
| 316 | + |
| 317 | + |
| 318 | +</body> |
| 319 | + |
| 320 | +<script> |
| 321 | + // We expose some of the variables needed by the front end |
| 322 | + window.hexo_search_path = "search.json" |
| 323 | + window.hexo_root = "/" |
| 324 | + window.isPost = true |
| 325 | +</script> |
| 326 | +<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> |
| 327 | + |
| 328 | +<script src="/js/index.js"></script> |
| 329 | + |
| 330 | +<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script> |
| 331 | + |
| 332 | + |
| 333 | + |
| 334 | + |
| 335 | + |
| 336 | + |
| 337 | +</html> |
0 commit comments