TNO-S3 / WuppieFuzz

A coverage-guided REST API fuzzer developed on top of LibAFL
Apache License 2.0
93 stars 4 forks source link

Resolving external OpenAPI references #33

Open ringogroenewegen opened 1 month ago

ringogroenewegen commented 1 month ago

Is your feature request related to a problem? Please describe. WuppieFuzz makes uses of the openapiv3-extended rust crate to deserialize OpenAPI specs. In some cases, references in a spec refer to external files, but the Rust crate does not support that feature. An example of such an API spec (from here) shows this. The $ref tag points towards an external file:

responses:
  '200':
    description: OK
    schema:
      $ref: './common/Pet.yaml'
      # or
      # $ref: 'https://api.example.com/schemas/Pet.yaml'

And the Pet.yaml file would contain something like this:

type: object
properties:
  id:
    type: integer
    readOnly: true
  petType:
    type: string
  name:
    type: string
required:
  - id
  - petType
  - name

Without this feature, WuppieFuzz will crash if it encounters an external reference.

Describe the solution you'd like Ideally this feature would be implemented in WuppieFuzz, but possibly the openapiv3-extended crate needs to be forked and modified.

Describe alternatives you've considered I forked the openapiv3-extended repo from Github and attempted to make changes to the resolver. The original code looks like this:

impl RefOr<Schema> {
    pub fn resolve<'a>(&'a self, spec: &'a OpenAPI) -> &'a Schema {
        match self {
            RefOr::Reference { reference } => resolve_helper(reference, spec, &mut HashSet::new()),
            RefOr::Item(schema) => schema,
        }
    }
}

I attempted to use recursion to load the OpenAPI spec from the file that is referenced. To do this I added the following to the resolve_helper function:

if let Some((external_file, external_ref)) = reference.split_once('#') {
        if !external_file.is_empty() {
            let external_ref_string = format!("#{}", external_ref_str);
            let new_api = &get_api_spec(&PathBuf::from(external_file)).unwrap();
            return resolve_helper(&external_ref, &new_api, seen);

The problem with this is that the resolve function uses only references to schemas and api objects. Therefore if we want to create a new api object from the external file, we would need to store it somewhere in memory. Then we can create a reference to the api object and the schema inside of it. Unfortunately I have not been able to do this, it seems like it would take a large restructuring of the code.