# CORS - 错误配置与绕过 {{#include ../banners/hacktricks-training.md}} ## 什么是 CORS? 跨源资源共享 (CORS) 标准 **使服务器能够定义谁可以访问其资产** 以及 **哪些 HTTP 请求方法被外部来源允许**。 **同源** 策略要求 **请求** 资源的服务器和托管 **资源** 的服务器共享相同的协议(例如,`http://`)、域名(例如,`internal-web.com`)和 **端口**(例如,80)。在此策略下,仅允许来自同一域和端口的网页访问资源。 在 `http://normal-website.com/example/example.html` 的上下文中,同源策略的应用如下所示: | 访问的 URL | 是否允许访问? | | ----------------------------------------- | ------------------------------------- | | `http://normal-website.com/example/` | 是:相同的协议、域名和端口 | | `http://normal-website.com/example2/` | 是:相同的协议、域名和端口 | | `https://normal-website.com/example/` | 否:不同的协议和端口 | | `http://en.normal-website.com/example/` | 否:不同的域名 | | `http://www.normal-website.com/example/` | 否:不同的域名 | | `http://normal-website.com:8080/example/` | 否:不同的端口\* | \*Internet Explorer 在执行同源策略时忽略端口号,因此允许此访问。 ### `Access-Control-Allow-Origin` 头 此头可以允许 **多个来源**、**`null`** 值或通配符 **`*`**。然而,**没有浏览器支持多个来源**,并且使用通配符 `*` 受到 **限制**。(通配符必须单独使用,且与 `Access-Control-Allow-Credentials: true` 一起使用是不允许的。) 此头是 **由服务器发出** 的,以响应由网站发起的跨域资源请求,浏览器会自动添加 `Origin` 头。 ### `Access-Control-Allow-Credentials` 头 **默认情况下**,跨源请求是在没有凭据(如 cookies 或 Authorization 头)的情况下进行的。然而,跨域服务器可以通过将 `Access-Control-Allow-Credentials` 头设置为 **`true`** 来允许在发送凭据时读取响应。 如果设置为 `true`,浏览器将传输凭据(cookies、授权头或 TLS 客户端证书)。 ```javascript var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { console.log(xhr.responseText) } } xhr.open("GET", "http://example.com/", true) xhr.withCredentials = true xhr.send(null) ``` ```javascript fetch(url, { credentials: "include", }) ``` ```javascript const xhr = new XMLHttpRequest() xhr.open("POST", "https://bar.other/resources/post-here/") xhr.setRequestHeader("X-PINGOTHER", "pingpong") xhr.setRequestHeader("Content-Type", "application/xml") xhr.onreadystatechange = handler xhr.send("Arun") ``` ### CSRF 预检请求 ### 理解跨域通信中的预检请求 在特定条件下发起跨域请求时,例如使用 **非标准 HTTP 方法**(除了 HEAD、GET、POST 以外),引入新的 **头部**,或使用特殊的 **Content-Type 头部值**,可能需要进行预检请求。这个初步请求利用 **`OPTIONS`** 方法,旨在通知服务器即将到来的跨源请求的意图,包括它打算使用的 HTTP 方法和头部。 **跨源资源共享 (CORS)** 协议要求进行此预检检查,以通过验证允许的方法、头部和来源的可信度来确定请求的跨源操作的可行性。有关哪些条件可以绕过预检请求的详细理解,请参考 [**Mozilla 开发者网络 (MDN)**](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) 提供的综合指南。 需要注意的是,**缺少预检请求并不意味着响应不需要携带授权头部**。没有这些头部,浏览器将无法处理来自跨源请求的响应。 考虑以下示例,展示了一个旨在使用 `PUT` 方法和名为 `Special-Request-Header` 的自定义头部的预检请求: ``` OPTIONS /info HTTP/1.1 Host: example2.com ... Origin: https://example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Authorization ``` 作为响应,服务器可能会返回指示接受的方法、允许的来源和其他CORS政策细节的头部,如下所示: ```markdown HTTP/1.1 204 No Content ... Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Methods: PUT, POST, OPTIONS Access-Control-Allow-Headers: Authorization Access-Control-Allow-Credentials: true Access-Control-Max-Age: 240 ``` - **`Access-Control-Allow-Headers`**: 此头部指定在实际请求中可以使用哪些头部。它由服务器设置,以指示来自客户端的请求中允许的头部。 - **`Access-Control-Expose-Headers`**: 通过此头部,服务器通知客户端哪些头部可以作为响应的一部分被暴露,除了简单的响应头部。 - **`Access-Control-Max-Age`**: 此头部指示预检请求的结果可以被缓存多长时间。服务器设置预检请求返回的信息可以重用的最大时间(以秒为单位)。 - **`Access-Control-Request-Headers`**: 在预检请求中使用,此头部由客户端设置,以通知服务器客户端希望在实际请求中使用哪些HTTP头部。 - **`Access-Control-Request-Method`**: 此头部也在预检请求中使用,由客户端设置,以指示在实际请求中将使用哪个HTTP方法。 - **`Origin`**: 此头部由浏览器自动设置,指示跨源请求的来源。服务器使用它来评估是否应根据CORS策略允许或拒绝传入请求。 请注意,通常情况下(取决于内容类型和设置的头部)在**GET/POST请求中不会发送预检请求**(请求是**直接**发送的),但如果您想访问**响应的头部/主体**,它必须包含一个允许的 _Access-Control-Allow-Origin_ 头部。\ **因此,CORS并不能防止CSRF(但它可能有帮助)。** ### **本地网络请求预检请求** 1. **`Access-Control-Request-Local-Network`**: 此头部包含在客户端的请求中,以表明该查询旨在访问本地网络资源。它作为一个标记,通知服务器请求源自本地网络内。 2. **`Access-Control-Allow-Local-Network`**: 作为响应,服务器利用此头部来传达请求的资源被允许与本地网络外的实体共享。它作为跨不同网络边界共享资源的绿灯,确保在维护安全协议的同时实现受控访问。 一个**有效的响应允许本地网络请求**还需要在响应中包含头部 `Access-Controls-Allow-Local_network: true` : ``` HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Local-Network: true Content-Length: 0 ... ``` > [!WARNING] > 请注意,linux **0.0.0.0** IP 可以用来 **绕过** 这些要求以访问 localhost,因为该 IP 地址不被视为“本地”。 > > 如果使用 **本地端点的公共 IP 地址**(例如路由器的公共 IP),也可以 **绕过本地网络要求**。因为在多种情况下,即使正在访问 **公共 IP**,如果是 **来自本地网络**,也会被允许访问。 ### 通配符 请注意,即使以下配置看起来非常宽松: ```bash Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true ``` 这在浏览器中是不允许的,因此凭据不会随请求发送。 ## 可利用的错误配置 已观察到将 `Access-Control-Allow-Credentials` 设置为 **`true`** 是大多数 **真实攻击** 的前提条件。此设置允许浏览器发送凭据并读取响应,从而增强攻击的有效性。如果没有这个,利用用户的 cookies 变得不可行,从而降低了让浏览器发出请求的好处。 ### 例外:利用网络位置作为身份验证 存在一个例外情况,即受害者的网络位置作为身份验证的一种形式。这允许受害者的浏览器作为代理,绕过基于 IP 的身份验证以访问内网应用程序。这种方法在影响上与 DNS 重新绑定相似,但更容易利用。 ### `Origin` 在 `Access-Control-Allow-Origin` 中的反射 在现实场景中,`Origin` 头的值反射在 `Access-Control-Allow-Origin` 中在理论上是不太可能的,因为对这些头的组合有限制。然而,开发人员希望为多个 URL 启用 CORS 时,可能会通过复制 `Origin` 头的值动态生成 `Access-Control-Allow-Origin` 头。这种方法可能引入漏洞,特别是当攻击者使用一个看似合法的域名时,从而欺骗验证逻辑。 ```html ``` ### 利用 `null` 源 `null` 源在重定向或本地 HTML 文件等情况中指定,具有独特的地位。一些应用程序将此源列入白名单以促进本地开发,无意中允许任何网站通过沙箱 iframe 模仿 `null` 源,从而绕过 CORS 限制。 ```html ``` ```html ``` ### 正则表达式绕过技术 当遇到域名白名单时,测试绕过机会至关重要,例如将攻击者的域名附加到白名单域名或利用子域名接管漏洞。此外,用于域名验证的正则表达式可能会忽略域名命名约定中的细微差别,从而提供进一步的绕过机会。 ### 高级正则表达式绕过 正则表达式模式通常集中于字母数字、点 (.) 和连字符 (-) 字符,忽略其他可能性。例如,构造一个包含浏览器和正则表达式模式以不同方式解释的字符的域名,可以绕过安全检查。Safari、Chrome 和 Firefox 对子域名中下划线字符的处理说明了如何利用这些差异来规避域名验证逻辑。 **有关此绕过检查的更多信息和设置:** [**https://www.corben.io/advanced-cors-techniques/**](https://www.corben.io/advanced-cors-techniques/) **和** [**https://medium.com/bugbountywriteup/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397**](https://medium.com/bugbountywriteup/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397) ![https://miro.medium.com/v2/resize:fit:720/format:webp/1*rolEK39-DDxeBgSq6KLKAA.png](<../images/image (284).png>) ### 从子域名中的 XSS 开发人员通常会实施防御机制,以通过白名单允许请求信息的域名来保护免受 CORS 利用。尽管采取了这些预防措施,但系统的安全性并非万无一失。在白名单域名中,即使存在一个脆弱的子域名,也可能通过其他漏洞(例如 XSS(跨站脚本攻击))打开 CORS 利用的大门。 例如,考虑一个场景,其中域名 `requester.com` 被列入白名单以访问另一个域名 `provider.com` 的资源。服务器端配置可能如下所示: ```javascript if ($_SERVER["HTTP_HOST"] == "*.requester.com") { // Access data } else { // Unauthorized access } ``` 在此设置中,`requester.com` 的所有子域都被允许访问。然而,如果一个子域,例如 `sub.requester.com`,存在 XSS 漏洞,攻击者可以利用这个弱点。例如,拥有 `sub.requester.com` 访问权限的攻击者可以利用 XSS 漏洞绕过 CORS 策略,并恶意访问 `provider.com` 上的资源。 ### **特殊字符** PortSwigger 的 [URL validation bypass cheat sheet](https://portswigger.net/research/introducing-the-url-validation-bypass-cheat-sheet) 发现某些浏览器支持域名中的奇怪字符。 Chrome 和 Firefox 支持下划线 `_`,可以绕过用于验证 `Origin` 头的正则表达式: ``` GET / HTTP/2 Cookie: Origin: https://target.application_.arbitrary.com ``` ``` HTTP/2 200 OK Access-Control-Allow-Origin: https://target.application_.arbitrary.com Access-Control-Allow-Credentials: true ``` Safari 在接受域名中的特殊字符方面更加宽松: ``` GET / HTTP/2 Cookie: Origin: https://target.application}.arbitrary.com ``` ``` HTTP/2 200 OK Cookie: Access-Control-Allow-Origin: https://target.application}.arbitrary.com Access-Control-Allow-Credentials: true ``` ### **其他有趣的 URL 技巧** {{#ref}} ssrf-server-side-request-forgery/url-format-bypass.md {{#endref}} ### **服务器端缓存中毒** [**来自这项研究**](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties) 通过 HTTP 头注入利用服务器端缓存中毒,可能会引发存储的跨站脚本 (XSS) 漏洞。当应用程序未能对 `Origin` 头进行非法字符的清理时,这种情况就会发生,特别是对 Internet Explorer 和 Edge 用户而言。这些浏览器将 (0x0d) 视为合法的 HTTP 头终止符,从而导致 HTTP 头注入漏洞。 考虑以下请求,其中 `Origin` 头被操纵: ``` GET / HTTP/1.1 Origin: z[0x0d]Content-Type: text/html; charset=UTF-7 ``` Internet Explorer 和 Edge 将响应解释为: ``` HTTP/1.1 200 OK Access-Control-Allow-Origin: z Content-Type: text/html; charset=UTF-7 ``` 直接利用此漏洞使网络浏览器发送畸形头部并不可行,但可以使用像 Burp Suite 这样的工具手动生成精心构造的请求。此方法可能导致服务器端缓存保存响应,并无意中将其提供给其他人。精心构造的有效负载旨在将页面的字符集更改为 UTF-7,这是一种字符编码,通常与 XSS 漏洞相关,因为它能够以某种方式编码字符,使其在特定上下文中可以作为脚本执行。 有关存储型 XSS 漏洞的进一步阅读,请参见 [PortSwigger](https://portswigger.net/web-security/cross-site-scripting/stored)。 **注意**:利用 HTTP 头注入漏洞,特别是通过服务器端缓存中毒,强调了验证和清理所有用户提供输入(包括 HTTP 头)的重要性。始终采用包括输入验证在内的强大安全模型,以防止此类漏洞。 ### **客户端缓存中毒** [**来自此研究**](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties) 在这种情况下,观察到一个网页实例反射了一个自定义 HTTP 头的内容,但没有进行适当编码。具体来说,网页反射回 `X-User-id` 头中包含的内容,这可能包括恶意 JavaScript,如示例所示,其中头部包含一个 SVG 图像标签,旨在在加载时执行 JavaScript 代码。 跨源资源共享 (CORS) 策略允许发送自定义头部。然而,由于 CORS 限制,响应未被浏览器直接呈现,这种注入的实用性似乎有限。关键点在于考虑浏览器的缓存行为。如果未指定 `Vary: Origin` 头,则恶意响应可能会被浏览器缓存。随后,当导航到该 URL 时,此缓存响应可能会被直接呈现,绕过初始请求时的直接呈现需求。此机制通过利用客户端缓存增强了攻击的可靠性。 为了说明此攻击,提供了一个 JavaScript 示例,旨在在网页环境中执行,例如通过 JSFiddle。此脚本执行一个简单的操作:它向指定 URL 发送一个包含恶意 JavaScript 的自定义头的请求。在请求成功完成后,它尝试导航到目标 URL,如果响应在没有正确处理 `Vary: Origin` 头的情况下被缓存,可能会触发注入脚本的执行。 以下是用于执行此攻击的 JavaScript 的简要分解: ```html ``` ## 绕过 ### XSSI (跨站脚本包含) / JSONP XSSI,也称为跨站脚本包含,是一种利用同源策略(SOP)在使用脚本标签包含资源时不适用的漏洞。这是因为脚本需要能够从不同域中包含。此漏洞允许攻击者访问和读取任何通过脚本标签包含的内容。 当涉及动态JavaScript或JSONP(带填充的JSON)时,这个漏洞变得尤为重要,特别是当使用像cookies这样的环境权限信息进行身份验证时。当从不同主机请求资源时,cookies会被包含,使攻击者可以访问。 为了更好地理解和缓解此漏洞,您可以使用可在[https://github.com/kapytein/jsonp](https://github.com/kapytein/jsonp)找到的BurpSuite插件。此插件可以帮助识别和解决您Web应用程序中的潜在XSSI漏洞。 [**在这里阅读有关不同类型的XSSI及其利用方式的更多信息。**](xssi-cross-site-script-inclusion.md) 尝试在请求中添加一个**`callback`** **参数**。也许该页面已准备好将数据作为JSONP发送。在这种情况下,页面将以`Content-Type: application/javascript`返回数据,从而绕过CORS策略。 ![](<../images/image (856).png>) ### 简单(无用?)绕过 绕过`Access-Control-Allow-Origin`限制的一种方法是请求Web应用程序代表您发出请求并发送回响应。然而,在这种情况下,最终受害者的凭据不会被发送,因为请求是发往不同的域。 1. [**CORS-escape**](https://github.com/shalvah/cors-escape):此工具提供一个代理,转发您的请求及其头,同时伪造Origin头以匹配请求的域。这有效地绕过了CORS政策。以下是使用XMLHttpRequest的示例: 2. [**simple-cors-escape**](https://github.com/shalvah/simple-cors-escape):此工具提供了一种替代的请求代理方法。服务器不是直接传递您的请求,而是使用指定的参数发出自己的请求。 ### Iframe + 弹出窗口绕过 您可以通过**创建一个iframe**并**从中打开一个新窗口**来**绕过CORS检查**,例如`e.origin === window.origin`。更多信息请参见以下页面: {{#ref}} xss-cross-site-scripting/iframes-in-xss-and-csp.md {{#endref}} ### 通过TTL进行DNS重绑定 通过TTL进行DNS重绑定是一种通过操纵DNS记录来绕过某些安全措施的技术。其工作原理如下: 1. 攻击者创建一个网页并使受害者访问它。 2. 攻击者随后将自己域的DNS(IP)更改为指向受害者的网页。 3. 受害者的浏览器缓存DNS响应,可能具有TTL(生存时间)值,指示DNS记录应被视为有效的时间。 4. 当TTL过期时,受害者的浏览器发出新的DNS请求,允许攻击者在受害者的页面上执行JavaScript代码。 5. 通过保持对受害者IP的控制,攻击者可以在不向受害者服务器发送任何cookies的情况下收集受害者的信息。 需要注意的是,浏览器具有缓存机制,可能会阻止立即滥用此技术,即使TTL值较低。 DNS重绑定对于绕过受害者执行的显式IP检查或用户或机器人在同一页面上停留较长时间的场景非常有用,从而允许缓存过期。 如果您需要快速滥用DNS重绑定,可以使用像[https://lock.cmpxchg8b.com/rebinder.html](https://lock.cmpxchg8b.com/rebinder.html)这样的服务。 要运行自己的DNS重绑定服务器,您可以利用像**DNSrebinder**([https://github.com/mogwailabs/DNSrebinder](https://github.com/mogwailabs/DNSrebinder))这样的工具。这涉及到暴露您的本地53/udp端口,创建一个指向它的A记录(例如,ns.example.com),并创建一个指向先前创建的A子域的NS记录(例如,ns.example.com)。ns.example.com子域的任何子域将由您的主机解析。 您还可以探索一个公开运行的服务器[http://rebind.it/singularity.html](http://rebind.it/singularity.html)以进一步理解和实验。 ### 通过**DNS缓存洪水**进行DNS重绑定 通过DNS缓存洪水进行DNS重绑定是另一种用于绕过浏览器缓存机制并强制第二个DNS请求的技术。其工作原理如下: 1. 最初,当受害者发出DNS请求时,响应为攻击者的IP地址。 2. 为了绕过缓存防御,攻击者利用服务工作者。服务工作者洪水式地填充DNS缓存,有效地删除了缓存的攻击者服务器名称。 3. 当受害者的浏览器发出第二个DNS请求时,现在响应为IP地址127.0.0.1,通常指向本地主机。 通过用服务工作者填充DNS缓存,攻击者可以操纵DNS解析过程并强制受害者的浏览器发出第二个请求,这次解析为攻击者所需的IP地址。 ### 通过**缓存**进行DNS重绑定 绕过缓存防御的另一种方法是利用DNS提供商中同一子域的多个IP地址。其工作原理如下: 1. 攻击者在DNS提供商中为同一子域设置两个A记录(或一个具有两个IP的单个A记录)。 2. 当浏览器检查这些记录时,它接收到两个IP地址。 3. 如果浏览器决定首先使用攻击者的IP地址,攻击者可以提供一个有效负载,执行对同一域的HTTP请求。 4. 然而,一旦攻击者获得受害者的IP地址,他们就停止响应受害者的浏览器。 5. 受害者的浏览器在意识到该域无响应后,转而使用第二个给定的IP地址。 6. 通过访问第二个IP地址,浏览器绕过同源策略(SOP),允许攻击者滥用这一点并收集和外泄信息。 此技术利用了浏览器在为域提供多个IP地址时的行为。通过战略性地控制响应并操纵浏览器的IP地址选择,攻击者可以利用SOP并访问受害者的信息。 > [!WARNING] > 请注意,为了访问localhost,您应该尝试在Windows中重新绑定**127.0.0.1**,在Linux中重新绑定**0.0.0.0**。\ > 像godaddy或cloudflare这样的提供商不允许我使用IP 0.0.0.0,但AWS route53允许我创建一个具有两个IP的A记录,其中一个是"0.0.0.0" > > 有关更多信息,您可以查看[https://unit42.paloaltonetworks.com/dns-rebinding/](https://unit42.paloaltonetworks.com/dns-rebinding/) ### 其他常见绕过 - 如果**不允许内部IP**,他们可能**忘记禁止0.0.0.0**(在Linux和Mac上有效) - 如果**不允许内部IP**,则响应**CNAME**到**localhost**(在Linux和Mac上有效) - 如果**不允许内部IP**作为DNS响应,您可以响应**CNAME到内部服务**,例如www.corporate.internal。 ### DNS重绑定武器化 您可以在演讲[Gerald Doussot - State of DNS Rebinding Attacks & Singularity of Origin - DEF CON 27 Conference](https://www.youtube.com/watch?v=y9-0lICNjOQ)中找到有关先前绕过技术的更多信息以及如何使用以下工具。 [**`Singularity of Origin`**](https://github.com/nccgroup/singularity)是一个执行[DNS重绑定](https://en.wikipedia.org/wiki/DNS_rebinding)攻击的工具。它包括将攻击服务器DNS名称的IP地址重新绑定到目标机器的IP地址所需的组件,并向目标机器提供攻击有效负载以利用脆弱软件。 ### 针对DNS重绑定的真正保护 - 在内部服务中使用TLS - 请求身份验证以访问数据 - 验证Host头 - [https://wicg.github.io/private-network-access/](https://wicg.github.io/private-network-access/):提议在公共服务器想要访问内部服务器时始终发送预检请求 ## **工具** **模糊可能的CORS政策配置错误** - [https://portswigger.net/bappstore/420a28400bad4c9d85052f8d66d3bbd8](https://portswigger.net/bappstore/420a28400bad4c9d85052f8d66d3bbd8) - [https://github.com/chenjj/CORScanner](https://github.com/chenjj/CORScanner) - [https://github.com/lc/theftfuzzer](https://github.com/lc/theftfuzzer) - [https://github.com/s0md3v/Corsy](https://github.com/s0md3v/Corsy) - [https://github.com/Shivangx01b/CorsMe](https://github.com/Shivangx01b/CorsMe) - [https://github.com/omranisecurity/CorsOne](https://github.com/omranisecurity/CorsOne) ## 参考 - [https://portswigger.net/web-security/cors](https://portswigger.net/web-security/cors) - [https://portswigger.net/web-security/cors/access-control-allow-origin](https://portswigger.net/web-security/cors/access-control-allow-origin) - [https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#CORS) - [https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties) - [https://www.codecademy.com/articles/what-is-cors](https://www.codecademy.com/articles/what-is-cors) - [https://www.we45.com/blog/3-ways-to-exploit-misconfigured-cross-origin-resource-sharing-cors](https://www.we45.com/blog/3-ways-to-exploit-misconfigured-cross-origin-resource-sharing-cors) - [https://medium.com/netscape/hacking-it-out-when-cors-wont-let-you-be-great-35f6206cc646](https://medium.com/netscape/hacking-it-out-when-cors-wont-let-you-be-great-35f6206cc646) - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CORS%20Misconfiguration](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CORS%20Misconfiguration) - [https://medium.com/entersoftsecurity/every-bug-bounty-hunter-should-know-the-evil-smile-of-the-jsonp-over-the-browsers-same-origin-438af3a0ac3b](https://medium.com/entersoftsecurity/every-bug-bounty-hunter-should-know-the-evil-smile-of-the-jsonp-over-the-browsers-same-origin-438af3a0ac3b) {{#include ../banners/hacktricks-training.md}}