wSAST Code Analyzer

Flexible static code analysis framework for consultants and developers.

Dataflow Analysis

The wSAST framework contains a fully functional source-to-sink dataflow analysis engine. The engine is designed to simulate execution of all possible code paths within the application under analysis, executing sources and sinks against each node as it proceeds.

Powerful Source & Sink Creation

Sinks are either written against the provided Common Rules Engine using an XML-based format which enables functions, variables, types and data source and sinks to be described easily without code, or can be imported as custom .NET plugins implementing the appropriate interface.

Sources and sinks can mark variables as linked using arbitrary relationships (given by name, e.g. variable Len is the "LENGTH_OF" variable BUF, and links must be reciprocal, so BUF is also specified as the BUFFER_FOR variable Len) and these relationships are maintained throughout execution, even when variables are aliased or copied.

Arbitrary data can be attached by name to any variable, allowing for especially complicated rules to be built up by the sinks implemented. This enables rules to be written which work around any limitations or design conflicts of the WSIL language to which all processed code is lowered, if such are found to exist. This capability could even be used to implement an interpreter for the language under analysis if so desired.

An example of the Common Rules Engine format includes:

Function Sink

<function name="MemCpySink" languages="wsil, c" report="true" categories="MEM_CORRUPTION" title="Potentially dangerous copy operation." description="The function has been known to lead to memory corruption vulnerabilities when incorrectly employed; review usage.">
	<signature names="MemCpy, memcpy, CopyMemory, RtlCopyMemory, memmove" param-count="3" />
	<param pos="1" name="dst" types="uint8\[\]"/>
	<param pos="2" name="src" types="uint8\[\]" traced="true" linked-param="3:BUFFER_FOR:"/>
	<param pos="3" name="len" types="int32" traced="true" linked-param="2:LENGTH_OF:"/>
</function>

This can be used to define functions as sinks, specifying parameter types, counts, alternative names, arbitrary categories of vulnerability they can cause (e.g. "MEM_CORRUPTION"), as well as whether the function is a member of any class or particular namespace and whether it is virtual or not (if it is then the class hierarchy will be searched for each potential instance). Variable links can also be validated via the linked-param which specifies the parameter number and the relationship (and optionally its reciprocal).

Variable Name Sink
<variable name="WriteToPasswordVariable" languages="*" report="true" categories="MEM_CORRUPTION" title="Writes to a variable named password." description="The code writes to a local or member variable named password." >
	<definition prefix-types="UserModel, .*" prefix-virtual="true" types=".*" virtual="true" names="[pP]ass(wd|word)?" access="write" traced="true" />
</variable>

This can be used to define variables or data members which can act as sinks, for example specific fields of a Crypto object for example specifying the cipher in use, or fields such as those named "Password". The variable or member name is matched using regular expressions, as is the containg class or namespace. The access type (read or write) can also be configured, and for the "write" case whether the variable on the right hand side of the assignment should be traced for the sink to trigger. For type comparison class hierarchies can also be considered if virtual.

Data Sink

<data name="SinkInsertOrUpdate" languages="*" report="true" categories="SQL_INJECTION" title="SQL Query Sink" description="Writes data containing a SQL query">
	<definition types="string" value="(SELECT|UPDATE)\s+.*" traced="true" />
</data>

This can be used to define data values which act as a sink when combined with a traced input, for example a fragment of an SQL statement being concatenated to a user-supplied input. Regular expressions are used to match values.

In addition to the sinks above, a near identical source format exists for each of the above Common Rules XML formats specifying in addition parameter variable relationships, which parameters/outputs are traced, and so forth.

Source & Sink Creation in .NET

For complete control over the implementation of a particular source or sink, for example to implement a rule which is not based on matching function, variable or data usage, it is possible to implement custom rules entirely in .NET. The following interfaces are provided for this purpose:


public interface ISource
{
	string GetSourceName();
	string[] GetSourceCategories();
	string GetDescription();
	bool GetMatchBeforeEval();
	bool IsMatch(WrappedDataflowEngine dfe, WrappedDataflowSource source, WrappedWsilNode node, WrappedDataflowStateIn state_in, WrappedDataflowStateOut dso);
	void DoAction(WrappedDataflowEngine dfe, WrappedDataflowSource source, WrappedWsilNode node, WrappedDataflowStateIn state_in, WrappedDataflowStateOut state_out);
}

public interface ISink
{
	string GetSinkName();
	string[] GetSinkCategories();
	string GetTitle();
	string GetDescription();
	bool GetMatchBeforeEval();
	bool IsMatch(WrappedDataflowEngine dfe, WrappedDataflowSink sink, WrappedWsilNode node, WrappedDataflowStateIn state_in, WrappedDataflowStateOut dso);
	void DoAction(WrappedDataflowEngine dfe, WrappedDataflowSink sink, WrappedWsilNode node, WrappedDataflowStateIn state_in, WrappedDataflowStateOut state_out);
}

The assembly containing the implementation is imported via the wSAST configuration file, and then for each node encountered during dataflow analysis the IsMatch() method is invoked. If this returns true the DoAction() method is invoked. Sinks have full access to the analyzer state at the point of execution including the ability to search for variables from the evaluated node scope, access to all variables and their state including any custom data attached, and can process, manipulate or use the Dataflow Engine to evaluate any part of the AST freely.

The Common Rules Engine additionally provides some classes for working with sources, sinks and ASTs, enabling easy implementation of new checks that work both statically and with the dataflow analyzer.

An example of implementing a check for a duplicated conditionals within if/else statements follows:


using System.Collections.Generic;
using System.Linq;
using CommonRulesEngine.Helpers;
using CommonRulesEngine.SimpleSyntactic;
using WsilHelper;

internal class DuplicatedIfElseConditionRule : SuspiciousConditionRule
{
	private static string _ruleName = "DuplicatedIfElseCondition";

	private static string _title = "Duplicated If/Else Condition";

	private static string _description = "A condition within a series of if/else statements is duplicated; this will result in an impossible code path.";

	public DuplicatedIfElseConditionRule()
	{
		base.RuleName = _ruleName;
		base.Title = _title;
		base.Description = _description;
	}

	protected override bool CheckStaticRule(WrappedWsilNode node)
	{
		if (node.GetName() != "if")
		{
			return false;
		}
		
		var ifElseStatementChainConditions = ASTHelpers.GetIfElseStatementChainConditions(node);
		
		if (ifElseStatementChainConditions.Count < 2)
		{
			return false;
		}
		
		var array = ifElseStatementChainConditions.DifferentCombinations(2).ToArray();
		
		foreach (var source in array)
		{
			WrappedWsilNode node1 = source.ElementAt(0);
			WrappedWsilNode node2 = source.ElementAt(1);
			
			if (ASTHelpers.CompareExpressions(node1, node2))
			{
				return true;
			}
		}
		
		return false;
	}

	protected override List<WrappedWsilNode> CheckDataflowRuleGetExpressions(WrappedDataflowEngine dfe, WrappedDataflowSink sink, WrappedWsilNode node, WrappedDataflowStateIn state_in, WrappedDataflowStateOut dso)
	{
		if (node.GetName() != "if")
		{
			return null;
		}
		
		var ifElseStatementChainConditions = ASTHelpers.GetIfElseStatementChainConditions(node);
		
		if (ifElseStatementChainConditions.Count < 2)
		{
			return null;
		}
		
		List<WrappedWsilNode> list = new List<WrappedWsilNode>();
		
		var array = ifElseStatementChainConditions.DifferentCombinations(2).ToArray();
		
		foreach (var source in array)
		{
			WrappedWsilNode node1 = source.ElementAt(0);
			WrappedWsilNode node2 = source.ElementAt(1);
			
			if (ASTHelpers.CompareExpressions(node1, node2))
			{
				list.Add(node1);
			}
		}
		return list;
	}
}

Code Flow

The analyzer first enumerates all entrypoints within the codebase, where an entrypoint is defined as a function or block of code which has no callers. Execution follows the code flow and sources mark variables as tainted, sinks become attached to the execution state, and both come in and out of scope according to the rules of the language being analysed.

The analyzer walks code following all method calls within the application, maintaining appropriate context for example when switching scopes, when entering class methods, and properly reconciling references to local block, method, inner and outer class scope whenever variables are accessed. The analyzer fully supports multiple inheritance and nested classes and interfaces, allowing complicated class structured to be accurately captured.

Robust Analysis

The analyzer is designed to be as robust as possible during analysis and to work well with missing or ill-defined code. If types are used for which code is missing the analyzer will infer the structures of the types from their usage, and record that information as execution proceeds. For missing types and methods the analyzer has a number of high-level controls which enable its taint logic to be defined.

  • Tainted instance taints all data members (ON/OFF)
  • Tainted instance taints all member function outputs (ON/OFF)
  • Tainted member function input taints function outputs (ON/OFF)
  • Tainted function input taints instance (ON/OFF)

For more nuanced analysis of missing code it is possible to "shim" the missing code using WSIL code which can then be included for analysis. For example, if a complicated data model class was missing where it was known for example that calling the model.setName() method with a tainted name would taint the associated model.getName() method, a simple WSIL source file containing code similar to ...

class Model
{
	void setName(string name) { _name = name; }
	string getName() { return _name; }
}

... can be used to bridge the gap in analysis, enabling code to flow through seamlessly.

The analyzer can be configured to execute with resource constraints, for example the number of threads specified for simultaneous analysis, the maximum amount of memory analysis is allowed to consume, the maximum number of seconds allowed per execution path, and whether the analyzer should abort code that causes errors or continue analysis.

Reporting

Reporting is performed through an interface which enables reports to be generated for any format desired; the plugins are configured within the wSAST XML configuration file. The provided reporting plugin writes code to a text file with a name which can be customised based on source/sink name, finding category, date, and other factor. Either entire traces (as below) or partial traces can be logged.


Title: Potential SQL Injection
Source-to-Sink: getParameter -> executeQuery
Category: ALL_CATEGORIES
Source Description: Retrieves a request parameter from the query string or request body.
Sink Description: Use of executeQuery directly on tainted input.

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\controller\LoginValidator.java (line 43)]
       String user=request.getParameter("username").trim();

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\controller\LoginValidator.java (line 44)]
          String pass=request.getParameter("password").trim();

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\controller\LoginValidator.java (line 47)]
                 Connection con=new DBConnect().connect(getServletContext().getRealPath("/WEB-INF/config.properties"));

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 23)]
    public Connection connect(String path) throws IOException,ClassNotFoundException,SQLException

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 25)]
        Properties properties=new Properties();

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 26)]
        properties.load(new FileInputStream(path));

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 27)]
        String dbuser=properties.getProperty("dbuser");

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 28)]
         String dbpass = properties.getProperty("dbpass");

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 29)]
       String dbfullurl = properties.getProperty("dburl")+properties.getProperty("dbname");

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 30)]
       String jdbcdriver = properties.getProperty("jdbcdriver");

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 31)]
            Connection con=null;

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 34)]
                    Class.forName(jdbcdriver);

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 35)]
                    con= DriverManager.getConnection(dbfullurl,dbuser,dbpass);

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 36)]
                    return con;

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\model\DBConnect.java (line 39)]
                    {

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\controller\LoginValidator.java (line 48)]
                    if(con!=null && !con.isClosed())

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\controller\LoginValidator.java (line 50)]
                                   ResultSet rs=null;

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\controller\LoginValidator.java (line 51)]
                                   Statement stmt = con.createStatement();  

[C:\wsast\examples\JavaVulnerableLab-master\src\main\java\org\cysecurity\cspf\jvl\controller\LoginValidator.java (line 52)]
                                   rs=stmt.executeQuery("select * from users where username='"+user+"' and password='"+pass+"'");

Dataflow Analysis

Explorer the Dataflow Analyzer which forms the core capability of wSAST.

Learn more

Static Analysis

Learn more about the Static Analysis capabilities.

Learn more

Code Graphing

Discover the powerful code graphing capabilities which can aid you in code exploration.

Learn more

Code Searching

See how code searching can help you quickly determine possible paths to exploitation.

Learn more