Skip to content

Writing a detector

Philippe Arteau edited this page May 7, 2018 · 7 revisions

Outline

  1. Everything start with a test
  2. Configuration and description
  3. Detecting the vulnerability

Everything start with a test

FindSecurityBugs is project that support mostly Test Driven Development. Their are few reason why testing is crucial to the project.

  • First, you don't want to reload the plugin in an IDE or an external tool each time you need to test your rules. You can run quickly FindSecurityBugs against specific samples.
  • Second, the number of code variation is infinite. When rules or utility classes are modified, we need to make sure that nothing was broken.
  • Third, test samples are importants because it's easy to write a rule that seems to find the right signature but doesn't. (Blacebo code)

1. Creating vulnerable samples

First create a simple class that illustrate the weakness you are trying to illustrate. Vulnerable sample are place in find-sec-bugs/plugin/src/test/java/testcode.

The code sample doesn't need to be a working application. Write the minimum code to trigger your rule.

For example, this class is create to illustrate deserialization of XML done with XMLDecoder:

public class XmlDecodeUtil {

    public static Object handleXml(InputStream in) {
        XMLDecoder d = new XMLDecoder(in);
        try {
            return d.readObject(); //Deserialization happen here
        }
        finally {
            d.close();
        }
    }
}

2. Writing a test case

You can copy-paste the basic test structure from a other test class. For example, here is a test that will validate that XmlDecodeUtil source will trigger the vulnerability "XML_DECODER" at the line 9.

public class XmlDecoderDetectorTest extends BaseDetectorTest {


    @Test
    public void detectXmlDecoder() throws Exception {
        //Locate test code
        String[] files = {
                getClassFilePath("testcode/xmldecoder/XmlDecodeUtil")
        };

        //Run the analysis
        EasyBugReporter reporter = spy(new EasyBugReporter());
        analyze(files, reporter);

        //Assertions
        verify(reporter).doReportBug(
                bugDefinition()
                        .bugType("XML_DECODER")
                        .inClass("XmlDecodeUtil").inMethod("handleXml").atLine(9)
                        .build()
        );
    }
}

You can run test and it should failed as you have not written any detector to identify the issue.

Configuration and description

3. Configuration of the new detector

findbugs.xml

    <Detector class="com.h3xstream.findsecbugs.XmlDecoderDetector" reports="XML_DECODER"/>
[...]
    <BugPattern type="XML_DECODER" abbrev="XMLDEC" category="SECURITY"/>

The class reference will soon be created.

4. Description

You need to create the appropriate description in the messages.xml. You can leave the Details XML node empty at this point and fill it later.

messages.xml

    <!-- XML decoder -->
    <Detector class="com.h3xstream.findsecbugs.XmlDecoderDetector">
        <Details>Identify use of XMLDecoder (a dangerous XML serializer).</Details>
    </Detector>
    <BugPattern type="XML_DECODER">
        <ShortDescription>XMLDecoder usage</ShortDescription>
        <LongDescription>It is not safe to use an XMLDecoder to parse user supplied data</LongDescription>
        <Details>
            <![CDATA[
<p>
    XMLDecoder should not be used to parse untrusted data. Deserializing user input can lead to arbitrary code execution.
    This is possible because XMLDecoder supports arbitrary method invocation. This capability is intended to call setter methods,
    but in practice, any method can be called.
</p>
[...]

            ]]>
        </Details>
    </BugPattern>
    <BugCode abbrev="XMLDEC">XMLDecoder usage</BugCode>

Detecting the vulnerability

5. New detector class

There are many possibility in order to implements a detector.

  • OpcodeStackDetector : Look for a specific method call
  • ConfiguredBasicInjectionDetector : To implement an injection-like detector
  • Detector : To analyze the complete class context

In order to detect the constructor call, we will use the InstructionDSL utility.

import static com.h3xstream.findsecbugs.common.matcher.InstructionDSL.*;

public class XmlDecoderDetector extends OpcodeStackDetector {


    private static final String XML_DECODER = "XML_DECODER";
    private static InvokeMatcherBuilder XML_DECODER_CONSTRUCTOR = invokeInstruction().atClass("java/beans/XMLDecoder").atMethod("<init>");

    private BugReporter bugReporter;
    
    public XmlDecoderDetector(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
    }

    @Override
    public void sawOpcode(int seen) {

        if (seen == Constants.INVOKESPECIAL && XML_DECODER_CONSTRUCTOR.matches(this)) {
            bugReporter.reportBug(new BugInstance(this, XML_DECODER, Priorities.HIGH_PRIORITY) //
                    .addClass(this).addMethod(this).addSourceLine(this));
        }
    }
}

Additional references

Here are additional articles about more advanced detectors that were implemented in Find Security Bugs.