MATCH (w { Code: 'window' })<-[:AST_parentOf{RelationType: 'object'}]-(p { Type: 'MemberExpression' })-[:AST_parentOf{RelationType: 'property'}]->(l { Code: 'location' }), (p)<-[:AST_parentOf{RelationType: 'object'}]-(gp { Type: 'MemberExpression' })-[:AST_parentOf{RelationType: 'property'}]->(h { Code: 'hash' }), (gp)<-[:AST_parentOf { RelationType: 'left' }]-(t { Type: 'AssignmentExpression' })-[:AST_parentOf{RelationType: 'right'}]->(v), (t)<-[:AST_parentOf { RelationType: 'expression' }]-(topsource { Type: 'ExpressionStatement' }), (n { Type: 'Identifier' })<-[:AST_parentOf { RelationType: 'property' }]-(parent)<-[:AST_parentOf*1..3]-(topsink) WHERE v.Type = 'Identifier' OR v.Type = 'Literal' AND n.Code =~ '.*(ajax|open|fetch|XMLHttpRequest|send).*' AND (topsink.Type = 'ExpressionStatement' OR topsink.Type = 'VariableDeclaration') WITH v AS val, topsink AS si, topsource AS sc MATCH (topsource)<-[:PDG_parentOf*1..100 { Arguments: val.Code }]-(vv { Type: 'VariableDeclaration' })-[:AST_parentOf]->(decl { Type: 'VariableDeclarator' })-[:AST_parentOf { RelationType: 'init' }]->(lit { Type: 'Literal' }) WHERE val.Type = 'Identifier' RETURN CASE EXISTS( (sc)-[:CFG_parentOf|:PDG_parentOf*1..100]->(si) ) WHEN true Then CASE val.Type WHEN 'Identifier' Then collect ( DISTINCT lit) ELSE collect ( DISTINCT val) END WHEN false Then null END AS res

A Graph-based Security Analysis Framework for JavaScript and Client-side CSRF

Get Started Now!

Publication

This work is accepted at the USENIX Security'21 conference. You can find a pre-print of our paper here.

Please consider supporting our work by citing our paper.

BibTex

@inproceedings{JAW,
  title = {JAW: Studying Client-side CSRF with Hybrid Property Graphs and Declarative Traversals},
  author= {Soheil Khodayari and Giancarlo Pellegrino},
  booktitle = {30th {USENIX} Security Symposium ({USENIX} Security 21)},
  year = {2021},
  address = {Vancouver, B.C.},
  publisher = {{USENIX} Association},
  month = aug,
}
    

Overview

  • JAW is a hybrid, scalable framework to analyze client-side JavaScript programs for the detection of client-side CSRF vulnerabilities.
  • JAW can be used to conduct interactive and exploratory analysis of JavaScript code
  • JAW can be used to design and perform custom security-related program analyses. For example:
    • data flow analysis between pre-defined JavaScript sources and sinks
    • control flow and reachability analysis
    • resolution of DOM query selectors leveraging DOM snapshots
    • pattern matching via the abstract syntax tree

How It Works?

  • JAW is based on property graphs and the neo4j graph database.
  • It creates a graph database for the client-side JavaScript program under test, capturing both static and dynamic program behaviours.
  • JAW models vulnerability detection as a fine-grained path traversal within the neo4j database.
  • The path traversal query can be performed using the Cypher query language.

Step 1: Graph Database Construction

Model your desired JavaScript program as a propery graph database in Neo4j.
              $ python3 -m hpg_construction.api base-folder-path --js=file-name.js
              
Learn more about the data model of the constructed property graph, including the nodes, edges , and the syntax tree .
You can also try JAW's built-in client-side CSRF detection module, or other program analysis utility queries (e.g., data flow analysis, reachability analysis, pattern matching, etc). See documentation for more.

Step 2: Security Analysis

Run your custom Cypher queries over the neo4j database.
  #!/usr/bin/python3
  from neo4j import GraphDatabase
  import constants as constantsModule

  # setup connection to the graph database
  neo_driver = GraphDatabase.driver(constantsModule.NEO4J_CONN_STRING, 
    auth=(constantsModule.NEO4J_USER, constantsModule.NEO4J_PASS))

  # query the graph
  with neo_driver.session() as session:
    with session.begin_transaction() as tx:

      # let's say we want to find all `fetch()` API calls and their URL arguments
      # t= top level expression statement, n = callExpression, a= URL argument
      query = """
      MATCH (t {Type: 'ExpressionStatement'})-[:AST_parentOf {RelationType: 'expression'}]->(n {Type: 'CallExpression'})-[:AST_parentOf {RelationType: 'callee'}]-> (req {Type: 'Identifier', Code: 'fetch'}), 
      (n)-[:AST_parentOf {RelationType: 'arguments', Arguments: '{\"arg\":0}'}]->(a)
      RETURN t, n, a
      """
      # execute the query and print the results
      results= tx.run(query)
      for record in results:
        print(record['n'])