LFGA Web

Week 10 - What I learned

This week in the academy program I had to do a lot of things that could be intimidating but I still keep going and learning. This week I had to my first contribution to an open-source project once again having nothing and reading other people's code. Also, this week was of a lot of learning and remembering things about computer science concepts and also learning soft skills in this case communication with others, people we don't know. This was quite an interesting week so let's get to it.

I'm going to share the main objectives of the week, what I did, talk a little more about the project I'm working on, what I had to do to get to my first contribution, and finally the results. I learn a lot of things this week, I will try to enum them here hope to put everything and don't forget something.

Goals of the week.

I think that the main goal was to understand the project that everyone is working on and try to find a way to solve an issue. In my case working with the vulnerable app I chose a big issue, which is adding new whole functionality from the ground, this means that I had to design a new level and find use cases and find the way to code it. That's personally from my project.

On the other hand, I had to answer some questions about other languages installing the environment and answering from the code.

Finally other of the goals of the week was trying to remember concepts for a mock interview, computer science concepts that I used to know but I forgot so I just need to go over them again and that's all.

Open-source

As I said I chose a big issue, I mean I don't mind I like the application, the purpose of the application, and the things that I have to do to solve the problem. Here is the issue:

how

To do this I need to:

Facing the first problem understanding the vulnerability and trying to design an application part where to practice. I went out to the internet watched some videos and kind of find a working solution and I did that proposal and it was accepted. I also did the SSRF section in the Portswigger web security academy in where you can find information about the vulnerability and it has a practical part, the labs part.

how

From here I also got new ideas to work on the following levels for the application, that I will share with you just let me define how is going to work. In this PR I will try to do level one add the codebase and the first level. The first level consists of an image loader you can enter an URL and the server is going to send a request to load the image and show it in the front-end, the vulnerability comes into play when you call an internal resource using the file:/// protocol.

Things to do here, do the front-end and back-end code. Find the vulnerable code. Here I had to do a little bit of research but I managed to find an example of the implementation to the vulnerability I had to take lines and mix them with others, I had to do little adjustments. That's how I did the back-end and the front-end I just used the classical JS and HTML so that wasn't a big problem.

I tested the code and everything was working fine, so now it's time to do the PR. That's the next problem that I faced is to write a good proper PR so that the reviewers could understand what I was doing and the solution that I was proposing. This part of communication and I think personally that people want to read concrete and short things that contain just enough information I tried to that and also include some images about the new features.

how

You can find more info about the project here: VulnerableApp

Now... the results...

I got feedback from the PR and I think that it was good feedback, I mean I got some code corrections but in general, my work is good. I think I'm going to keep working on this project and I know that I can add more value and I add new levels and different vulnerabilities. The problem here is that I have to do some use-case research too... and that's the hard part, but... let's do it.

I also want to mention that I really strived to do a good PR description and to make it easy for reviews to go through and my effort was rewarded.

how

Now things to do in the next week:

Let's get to it and see what happens this week.


Now, I'm going to share the questions and answers.


How Java annotations add or inject behavior to classes or methods?


What is the problem?

The main problem that I encountered is that when I first saw the application I saw that we had to create methods and only add annotations and I tough that by adding annotations we added some behavior to a method or class. Today I learned something new.

I want to show you how a method is declared and what it does:

@AttackVector(
            vulnerabilityExposed = VulnerabilitySubType.COMMAND_INJECTION,
            description = "COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED")
    @VulnerableAppRequestMapping(
            value = LevelConstants.LEVEL_1,
            descriptionLabel = "COMMAND_INJECTION_URL_CONTAINING_IPADDRESS",
            htmlTemplate = "LEVEL_1/CI_Level1",
            parameterName = IP_ADDRESS,
            sampleValues = {"localhost"})
    public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel1(
            @RequestParam(IP_ADDRESS) String ipAddress) throws IOException {
        Supplier<Boolean> validator = () -> StringUtils.isNotBlank(ipAddress);
        return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
                new GenericVulnerabilityResponseBean<String>(
                        this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
                        true),
                HttpStatus.OK);
    }
@AttackVector(
            vulnerabilityExposed = VulnerabilitySubType.COMMAND_INJECTION,
            description = "COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED")
    @VulnerableAppRequestMapping(
            value = LevelConstants.LEVEL_1,
            descriptionLabel = "COMMAND_INJECTION_URL_CONTAINING_IPADDRESS",
            htmlTemplate = "LEVEL_1/CI_Level1",
            parameterName = IP_ADDRESS,
            sampleValues = {"localhost"})
    public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel1(
            @RequestParam(IP_ADDRESS) String ipAddress) throws IOException {
        Supplier<Boolean> validator = () -> StringUtils.isNotBlank(ipAddress);
        return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
                new GenericVulnerabilityResponseBean<String>(
                        this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
                        true),
                HttpStatus.OK);
    }

By looking at the VulnerableAppRequestMapping annotation I tough "Well this is exposing the service and everything is working because of the annotation, the annotation is adding behavior to the method". Let's see if this is true that's why I have chosen this question.

What Are Annotations?

One word to explain annotation is metadata. Metadata is data about data. So annotations are metadata for code.

Java annotations are used to provide meta data for your Java code. Being meta data, Java annotations do not directly affect the execution of your code, although some types of annotations can actually be used for that purpose.

What I can understand from this is that we add these annotations that start with @ to a class, method, or attribute, and by doing this we define data about that code that's under the annotation, by providing this kind of data by describing something we can't modify the logic flow of the code. Here is the important thing, by giving more data, logic won't change but now other Java apps may use that data to do something like using it in different situations.

We now know that the annotations are not doing something logical in the source code that we are working on let's now dissect the annotation.

Dissecting the VulnerableAppRequestMapping annotation

This is the code of the annotation:

package org.sasanlabs.internal.utility.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.sasanlabs.internal.utility.Variant;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/** @author KSASAN preetkaran20@gmail.com */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
@RequestMapping
public @interface VulnerableAppRequestMapping {

    /**
     * Specify the level of the vulnerability. Url end point is exposed for each level.
     *
     * @return level
     */
    @AliasFor(annotation = RequestMapping.class)
    String value();

    /**
     * Specify whether the implementation can be considered secure, as in, non-exploitable.
     *
     * @return variant
     */
    Variant variant() default Variant.UNSECURE;

    /**
     * Describes the information about the input type, expected output and other factors. like say
     * input is needed as a URL or as a Cookie etc.
     *
     * @return Localization Key
     */
    String descriptionLabel() default "EMPTY_LABEL";

    /**
     * Template name is used to construct the url for static resources like js/css/html. UI will
     * look for path to static templates like static/templates/JWTVulnerabilities/{htmlTemplate}.{js
     * or css or html} to construct final Html.
     *
     * @return template name
     */
    String htmlTemplate() default "";

    /**
     * This information can be used by Scanners to know location in request where payload can be
     * injected. Default value is {@link RequestParameterLocation#QUERY_PARAM}
     *
     * @return location of parameter
     */
    RequestParameterLocation requestParameterLocation() default
            RequestParameterLocation.QUERY_PARAM;

    /**
     * This information can be used by Scanners to know the name of the key whose value is read by
     * the endpoint/vulnerability level for performing the operation.
     *
     * @return name of the parameter which holds the value
     */
    String parameterName() default "";

    /**
     * This information is very useful for scanners in case of attacks which depends on the sample
     * values like JWT. In case of JWT there are two steps for the attack: 1. Fetch the token 2.
     * Fuzz and try
     *
     * <p>Sample values can be very helpful in removing the step 1.
     *
     * @return array of sample values.
     */
    String[] sampleValues() default {};

    /**
     * This information is not useful for now because we are for now only handling Get requests but
     * going further we might use this information and hence this is very useful for scanner.
     *
     * @return {@code RequestMethod} for the Level
     */
    @AliasFor(attribute = "method", annotation = RequestMapping.class)
    RequestMethod requestMethod() default RequestMethod.GET;

    /**
     * ResponseType helps the implementer to know what is the type of response returned from the
     * rest/http api call. This is important for vulnerabilities which returns entire html or tags
     * like XSS vulnerability. The default responseType is {@link ResponseType#JSON}.
     *
     * @return ResonseType for the vulnerability level.
     */
    ResponseType responseType() default ResponseType.JSON;
}
package org.sasanlabs.internal.utility.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.sasanlabs.internal.utility.Variant;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/** @author KSASAN preetkaran20@gmail.com */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
@RequestMapping
public @interface VulnerableAppRequestMapping {

    /**
     * Specify the level of the vulnerability. Url end point is exposed for each level.
     *
     * @return level
     */
    @AliasFor(annotation = RequestMapping.class)
    String value();

    /**
     * Specify whether the implementation can be considered secure, as in, non-exploitable.
     *
     * @return variant
     */
    Variant variant() default Variant.UNSECURE;

    /**
     * Describes the information about the input type, expected output and other factors. like say
     * input is needed as a URL or as a Cookie etc.
     *
     * @return Localization Key
     */
    String descriptionLabel() default "EMPTY_LABEL";

    /**
     * Template name is used to construct the url for static resources like js/css/html. UI will
     * look for path to static templates like static/templates/JWTVulnerabilities/{htmlTemplate}.{js
     * or css or html} to construct final Html.
     *
     * @return template name
     */
    String htmlTemplate() default "";

    /**
     * This information can be used by Scanners to know location in request where payload can be
     * injected. Default value is {@link RequestParameterLocation#QUERY_PARAM}
     *
     * @return location of parameter
     */
    RequestParameterLocation requestParameterLocation() default
            RequestParameterLocation.QUERY_PARAM;

    /**
     * This information can be used by Scanners to know the name of the key whose value is read by
     * the endpoint/vulnerability level for performing the operation.
     *
     * @return name of the parameter which holds the value
     */
    String parameterName() default "";

    /**
     * This information is very useful for scanners in case of attacks which depends on the sample
     * values like JWT. In case of JWT there are two steps for the attack: 1. Fetch the token 2.
     * Fuzz and try
     *
     * <p>Sample values can be very helpful in removing the step 1.
     *
     * @return array of sample values.
     */
    String[] sampleValues() default {};

    /**
     * This information is not useful for now because we are for now only handling Get requests but
     * going further we might use this information and hence this is very useful for scanner.
     *
     * @return {@code RequestMethod} for the Level
     */
    @AliasFor(attribute = "method", annotation = RequestMapping.class)
    RequestMethod requestMethod() default RequestMethod.GET;

    /**
     * ResponseType helps the implementer to know what is the type of response returned from the
     * rest/http api call. This is important for vulnerabilities which returns entire html or tags
     * like XSS vulnerability. The default responseType is {@link ResponseType#JSON}.
     *
     * @return ResonseType for the vulnerability level.
     */
    ResponseType responseType() default ResponseType.JSON;
}

Now we have to remember that annotations are only metadata so the attributes in the class are only for that. So here in this class, we have those attributes. We can see that there is not code for logical stuff or something that could modify the logical flow of the program. We can also see that is annotated with the @RequestMapping annotation that tells Spring to threaten this differently.

Now let's just see where is this annotation used.

@AttackVector(
            vulnerabilityExposed = VulnerabilitySubType.COMMAND_INJECTION,
            description = "COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED")
    @VulnerableAppRequestMapping(
            value = LevelConstants.LEVEL_1,
            descriptionLabel = "COMMAND_INJECTION_URL_CONTAINING_IPADDRESS",
            htmlTemplate = "LEVEL_1/CI_Level1",
            parameterName = IP_ADDRESS,
            sampleValues = {"localhost"})
    public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel1(
            @RequestParam(IP_ADDRESS) String ipAddress) throws IOException {
        Supplier<Boolean> validator = () -> StringUtils.isNotBlank(ipAddress);
        return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
                new GenericVulnerabilityResponseBean<String>(
                        this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
                        true),
                HttpStatus.OK);
    }
@AttackVector(
            vulnerabilityExposed = VulnerabilitySubType.COMMAND_INJECTION,
            description = "COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED")
    @VulnerableAppRequestMapping(
            value = LevelConstants.LEVEL_1,
            descriptionLabel = "COMMAND_INJECTION_URL_CONTAINING_IPADDRESS",
            htmlTemplate = "LEVEL_1/CI_Level1",
            parameterName = IP_ADDRESS,
            sampleValues = {"localhost"})
    public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel1(
            @RequestParam(IP_ADDRESS) String ipAddress) throws IOException {
        Supplier<Boolean> validator = () -> StringUtils.isNotBlank(ipAddress);
        return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
                new GenericVulnerabilityResponseBean<String>(
                        this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
                        true),
                HttpStatus.OK);
    }

We are going to find this annotation on the methods that are in the vulnerabilities classes and is putting data that describes that method and adding the request mapping annotation. Let's see the data in this method:

Now we have a clearer idea of what is happening and what's the annotation doing to our method. In another place of the code, we could see that metadata and try to get the method. Now let's try to find that place. We need to know this because the annotations in this project are the basis for everything.

We found this class:

package org.sasanlabs.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.lang.reflect.Method;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.sasanlabs.beans.AllEndPointsResponseBean;
import org.sasanlabs.beans.AttackVectorResponseBean;
import org.sasanlabs.beans.LevelResponseBean;
import org.sasanlabs.beans.ScannerResponseBean;
import org.sasanlabs.configuration.VulnerableAppProperties;
import org.sasanlabs.internal.utility.EnvUtils;
import org.sasanlabs.internal.utility.FrameworkConstants;
import org.sasanlabs.internal.utility.GenericUtils;
import org.sasanlabs.internal.utility.MessageBundle;
import org.sasanlabs.internal.utility.annotations.AttackVector;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRequestMapping;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRestController;
import org.sasanlabs.service.IEndPointsInformationProvider;
import org.sasanlabs.vulnerability.types.VulnerabilityType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/** @author KSASAN preetkaran20@gmail.com */
@Service
public class EndPointsInformationProvider implements IEndPointsInformationProvider {

    private EnvUtils envUtils;

    private MessageBundle messageBundle;

    private VulnerableAppProperties vulnerableAppProperties;

    int port;

    public EndPointsInformationProvider(
            EnvUtils envUtils,
            MessageBundle messageBundle,
            VulnerableAppProperties vulnerableAppProperties,
            @Value("${server.port}") int port) {
        this.envUtils = envUtils;
        this.messageBundle = messageBundle;
        this.vulnerableAppProperties = vulnerableAppProperties;
        this.port = port;
    }

    @Override
    public List<AllEndPointsResponseBean> getSupportedEndPoints() throws JsonProcessingException {
        List<AllEndPointsResponseBean> allEndpoints = new ArrayList<>();
        Map<String, Object> nameVsCustomVulnerableEndPoint =
                envUtils.getAllClassesAnnotatedWithVulnerableAppRestController();
        for (Map.Entry<String, Object> entry : nameVsCustomVulnerableEndPoint.entrySet()) {
            String name = entry.getKey();
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(VulnerableAppRestController.class)) {
                VulnerableAppRestController vulnerableServiceRestEndPoint =
                        clazz.getAnnotation(VulnerableAppRestController.class);
                String description = vulnerableServiceRestEndPoint.descriptionLabel();
                VulnerabilityType[] vulnerabilityTypes = vulnerableServiceRestEndPoint.type();
                AllEndPointsResponseBean allEndPointsResponseBean = new AllEndPointsResponseBean();
                allEndPointsResponseBean.setName(name);
                allEndPointsResponseBean.setDescription(messageBundle.getString(description, null));
                allEndPointsResponseBean.setVulnerabilityTypes(vulnerabilityTypes);

                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    VulnerableAppRequestMapping vulnLevel =
                            method.getAnnotation(VulnerableAppRequestMapping.class);
                    if (vulnLevel != null) {
                        AttackVector[] attackVectors =
                                method.getAnnotationsByType(AttackVector.class);
                        LevelResponseBean levelResponseBean = new LevelResponseBean();
                        levelResponseBean.setLevel(vulnLevel.value());
                        levelResponseBean.setVariant(vulnLevel.variant());
                        levelResponseBean.setDescription(
                                messageBundle.getString(vulnLevel.descriptionLabel(), null));
                        levelResponseBean.setHtmlTemplate(vulnLevel.htmlTemplate());

                        levelResponseBean.setRequestParameterLocation(
                                vulnLevel.requestParameterLocation());
                        levelResponseBean.setParameterName(vulnLevel.parameterName());
                        levelResponseBean.setSampleValues(vulnLevel.sampleValues());
                        levelResponseBean.setRequestMethod(vulnLevel.requestMethod());
                        for (AttackVector attackVector : attackVectors) {
                            levelResponseBean
                                    .getAttackVectorResponseBeans()
                                    .add(
                                            new AttackVectorResponseBean(
                                                    new ArrayList<>(
                                                            Arrays.asList(
                                                                    attackVector
                                                                            .vulnerabilityExposed())),
                                                    vulnerableAppProperties.getAttackVectorProperty(
                                                            attackVector.payload()),
                                                    messageBundle.getString(
                                                            attackVector.description(), null)));
                        }
                        allEndPointsResponseBean.getLevelDescriptionSet().add(levelResponseBean);
                    }
                }
                allEndpoints.add(allEndPointsResponseBean);
            }
        }
        return allEndpoints;
    }

    @Override
    public List<ScannerResponseBean> getScannerRelatedEndPointInformation()
            throws JsonProcessingException, UnknownHostException {
        List<AllEndPointsResponseBean> allEndPointsResponseBeans = this.getSupportedEndPoints();
        List<ScannerResponseBean> scannerResponseBeans = new ArrayList<>();
        for (AllEndPointsResponseBean allEndPointsResponseBean : allEndPointsResponseBeans) {
            for (LevelResponseBean levelResponseBean :
                    allEndPointsResponseBean.getLevelDescriptionSet()) {
                for (AttackVectorResponseBean attackVectorResponseBean :
                        levelResponseBean.getAttackVectorResponseBeans()) {
                    scannerResponseBeans.add(
                            new ScannerResponseBean(
                                    new StringBuilder()
                                            .append(FrameworkConstants.HTTP)
                                            .append(GenericUtils.LOCALHOST)
                                            .append(FrameworkConstants.COLON)
                                            .append(port)
                                            .append(FrameworkConstants.SLASH)
                                            .append(allEndPointsResponseBean.getName())
                                            .append(FrameworkConstants.SLASH)
                                            .append(levelResponseBean.getLevel())
                                            .toString(),
                                    levelResponseBean.getLevel(),
                                    levelResponseBean.getVariant().toString(),
                                    levelResponseBean.getRequestParameterLocation(),
                                    levelResponseBean.getParameterName(),
                                    levelResponseBean.getSampleValues(),
                                    levelResponseBean.getRequestMethod(),
                                    attackVectorResponseBean.getVulnerabilityTypes()));
                }
            }
        }
        return scannerResponseBeans;
    }
}
package org.sasanlabs.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.lang.reflect.Method;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.sasanlabs.beans.AllEndPointsResponseBean;
import org.sasanlabs.beans.AttackVectorResponseBean;
import org.sasanlabs.beans.LevelResponseBean;
import org.sasanlabs.beans.ScannerResponseBean;
import org.sasanlabs.configuration.VulnerableAppProperties;
import org.sasanlabs.internal.utility.EnvUtils;
import org.sasanlabs.internal.utility.FrameworkConstants;
import org.sasanlabs.internal.utility.GenericUtils;
import org.sasanlabs.internal.utility.MessageBundle;
import org.sasanlabs.internal.utility.annotations.AttackVector;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRequestMapping;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRestController;
import org.sasanlabs.service.IEndPointsInformationProvider;
import org.sasanlabs.vulnerability.types.VulnerabilityType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/** @author KSASAN preetkaran20@gmail.com */
@Service
public class EndPointsInformationProvider implements IEndPointsInformationProvider {

    private EnvUtils envUtils;

    private MessageBundle messageBundle;

    private VulnerableAppProperties vulnerableAppProperties;

    int port;

    public EndPointsInformationProvider(
            EnvUtils envUtils,
            MessageBundle messageBundle,
            VulnerableAppProperties vulnerableAppProperties,
            @Value("${server.port}") int port) {
        this.envUtils = envUtils;
        this.messageBundle = messageBundle;
        this.vulnerableAppProperties = vulnerableAppProperties;
        this.port = port;
    }

    @Override
    public List<AllEndPointsResponseBean> getSupportedEndPoints() throws JsonProcessingException {
        List<AllEndPointsResponseBean> allEndpoints = new ArrayList<>();
        Map<String, Object> nameVsCustomVulnerableEndPoint =
                envUtils.getAllClassesAnnotatedWithVulnerableAppRestController();
        for (Map.Entry<String, Object> entry : nameVsCustomVulnerableEndPoint.entrySet()) {
            String name = entry.getKey();
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(VulnerableAppRestController.class)) {
                VulnerableAppRestController vulnerableServiceRestEndPoint =
                        clazz.getAnnotation(VulnerableAppRestController.class);
                String description = vulnerableServiceRestEndPoint.descriptionLabel();
                VulnerabilityType[] vulnerabilityTypes = vulnerableServiceRestEndPoint.type();
                AllEndPointsResponseBean allEndPointsResponseBean = new AllEndPointsResponseBean();
                allEndPointsResponseBean.setName(name);
                allEndPointsResponseBean.setDescription(messageBundle.getString(description, null));
                allEndPointsResponseBean.setVulnerabilityTypes(vulnerabilityTypes);

                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    VulnerableAppRequestMapping vulnLevel =
                            method.getAnnotation(VulnerableAppRequestMapping.class);
                    if (vulnLevel != null) {
                        AttackVector[] attackVectors =
                                method.getAnnotationsByType(AttackVector.class);
                        LevelResponseBean levelResponseBean = new LevelResponseBean();
                        levelResponseBean.setLevel(vulnLevel.value());
                        levelResponseBean.setVariant(vulnLevel.variant());
                        levelResponseBean.setDescription(
                                messageBundle.getString(vulnLevel.descriptionLabel(), null));
                        levelResponseBean.setHtmlTemplate(vulnLevel.htmlTemplate());

                        levelResponseBean.setRequestParameterLocation(
                                vulnLevel.requestParameterLocation());
                        levelResponseBean.setParameterName(vulnLevel.parameterName());
                        levelResponseBean.setSampleValues(vulnLevel.sampleValues());
                        levelResponseBean.setRequestMethod(vulnLevel.requestMethod());
                        for (AttackVector attackVector : attackVectors) {
                            levelResponseBean
                                    .getAttackVectorResponseBeans()
                                    .add(
                                            new AttackVectorResponseBean(
                                                    new ArrayList<>(
                                                            Arrays.asList(
                                                                    attackVector
                                                                            .vulnerabilityExposed())),
                                                    vulnerableAppProperties.getAttackVectorProperty(
                                                            attackVector.payload()),
                                                    messageBundle.getString(
                                                            attackVector.description(), null)));
                        }
                        allEndPointsResponseBean.getLevelDescriptionSet().add(levelResponseBean);
                    }
                }
                allEndpoints.add(allEndPointsResponseBean);
            }
        }
        return allEndpoints;
    }

    @Override
    public List<ScannerResponseBean> getScannerRelatedEndPointInformation()
            throws JsonProcessingException, UnknownHostException {
        List<AllEndPointsResponseBean> allEndPointsResponseBeans = this.getSupportedEndPoints();
        List<ScannerResponseBean> scannerResponseBeans = new ArrayList<>();
        for (AllEndPointsResponseBean allEndPointsResponseBean : allEndPointsResponseBeans) {
            for (LevelResponseBean levelResponseBean :
                    allEndPointsResponseBean.getLevelDescriptionSet()) {
                for (AttackVectorResponseBean attackVectorResponseBean :
                        levelResponseBean.getAttackVectorResponseBeans()) {
                    scannerResponseBeans.add(
                            new ScannerResponseBean(
                                    new StringBuilder()
                                            .append(FrameworkConstants.HTTP)
                                            .append(GenericUtils.LOCALHOST)
                                            .append(FrameworkConstants.COLON)
                                            .append(port)
                                            .append(FrameworkConstants.SLASH)
                                            .append(allEndPointsResponseBean.getName())
                                            .append(FrameworkConstants.SLASH)
                                            .append(levelResponseBean.getLevel())
                                            .toString(),
                                    levelResponseBean.getLevel(),
                                    levelResponseBean.getVariant().toString(),
                                    levelResponseBean.getRequestParameterLocation(),
                                    levelResponseBean.getParameterName(),
                                    levelResponseBean.getSampleValues(),
                                    levelResponseBean.getRequestMethod(),
                                    attackVectorResponseBean.getVulnerabilityTypes()));
                }
            }
        }
        return scannerResponseBeans;
    }
}

In this class, we can find the getSupportedEndPoints method. Let's try to go line by line and see what's happening

Map<String, Object> nameVsCustomVulnerableEndPoint =
        envUtils.getAllClassesAnnotatedWithVulnerableAppRestController();
Map<String, Object> nameVsCustomVulnerableEndPoint =
        envUtils.getAllClassesAnnotatedWithVulnerableAppRestController();

In this line is getting all the methods annotated with the VulnerableAppRestController annotation

We can do it with this

return context.getBeansWithAnnotation(VulnerableAppRestController.class);
return context.getBeansWithAnnotation(VulnerableAppRestController.class);

Then iterates over these methods, and is now USING THE METADA from the annotations:

String description = vulnerableServiceRestEndPoint.descriptionLabel();
VulnerabilityType[] vulnerabilityTypes = vulnerableServiceRestEndPoint.type();
AllEndPointsResponseBean allEndPointsResponseBean = new AllEndPointsResponseBean();
allEndPointsResponseBean.setName(name);
allEndPointsResponseBean.setDescription(messageBundle.getString(description, null));
allEndPointsResponseBean.setVulnerabilityTypes(vulnerabilityTypes);
String description = vulnerableServiceRestEndPoint.descriptionLabel();
VulnerabilityType[] vulnerabilityTypes = vulnerableServiceRestEndPoint.type();
AllEndPointsResponseBean allEndPointsResponseBean = new AllEndPointsResponseBean();
allEndPointsResponseBean.setName(name);
allEndPointsResponseBean.setDescription(messageBundle.getString(description, null));
allEndPointsResponseBean.setVulnerabilityTypes(vulnerabilityTypes);

And also is getting other information from other annotation declarations and it's creating a list of objects and return it.

In conclusion, in this project, we can see that annotations don't do something logical but a method annotated has data, extra data that we can define in a new annotation. With this, we can identify methods and their information, and now we can add some logical code to perform what we want with these methods.


This one of the questions that I have to answer so let's try to go step by step to finally get an answer, so first step is to learn what is Selenium and TestNG because there are commonly used together.

Selenium

Selenium is an umbrella project encapsulating a variety of tools and libraries enabling web browser automation.

Basically, what I can understand, it's a test framework in which you create code that executes a browser and actions on that browser to accomplish something in the Front-end.

TestNG

TestNG is a testing framework inspired from JUnit and NUnit but introducing some new functionalities that make it more powerful and easier to use.

Captura de Pantalla 2021-06-14 a la(s) 18.14.18.png

Now we can of understand whate they are but let's try to understand why they work together.

Why Use TestNG with Selenium?

The reasons that I can understand why these are used together are:

Annotations

I seems that @Factory and @DataProvider are Java annotations, let's try to find the source code for this two annotations.

Captura de Pantalla 2021-06-14 a la(s) 18.51.57.png

It seems that we can find the source code here.

Here we have the factory

Captura de Pantalla 2021-06-14 a la(s) 18.52.27.png

And the DataProvider also is here

Captura de Pantalla 2021-06-14 a la(s) 18.53.11.png

Data Provider

package org.testng.annotations;

import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * Mark a method as supplying data for a test method.
 *
 * <p>The {@link #name() name} defaults to the name of the annotated method.
 *
 * <p>The annotated method must return any of the following:
 *
 * <ul>
 *   <li>{@code Object[][]} or {@code Iterator<Object[]>}, where each {@code Object[]} is assigned
 *       to the parameter list of the test method.
 *   <li>{@code Object[]} or {@code Iterator<Object>}, where each {@code Object} is assigned to the
 *       single parameter of the test method.
 * </ul>
 *
 * <p>The {@link Test @Test} method that wants to receive data from this {@link DataProvider} needs
 * to use a {@link Test#dataProvider()} name equal to the name of this annotation.
 */
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD})
@Documented
public @interface DataProvider {

  /**
   * The name of this DataProvider.
   *
   * @return the value (default empty)
   */
  String name() default "";

  /**
   * Whether this data provider should be run in parallel.
   *
   * @return the value (default false)
   */
  boolean parallel() default false;

  /**
   * Which indices to run from this data provider, default: all.
   *
   * @return the value
   */
  int[] indices() default {};
}
package org.testng.annotations;

import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * Mark a method as supplying data for a test method.
 *
 * <p>The {@link #name() name} defaults to the name of the annotated method.
 *
 * <p>The annotated method must return any of the following:
 *
 * <ul>
 *   <li>{@code Object[][]} or {@code Iterator<Object[]>}, where each {@code Object[]} is assigned
 *       to the parameter list of the test method.
 *   <li>{@code Object[]} or {@code Iterator<Object>}, where each {@code Object} is assigned to the
 *       single parameter of the test method.
 * </ul>
 *
 * <p>The {@link Test @Test} method that wants to receive data from this {@link DataProvider} needs
 * to use a {@link Test#dataProvider()} name equal to the name of this annotation.
 */
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD})
@Documented
public @interface DataProvider {

  /**
   * The name of this DataProvider.
   *
   * @return the value (default empty)
   */
  String name() default "";

  /**
   * Whether this data provider should be run in parallel.
   *
   * @return the value (default false)
   */
  boolean parallel() default false;

  /**
   * Which indices to run from this data provider, default: all.
   *
   * @return the value
   */
  int[] indices() default {};
}

This is an annotation then, is to mark a method to be treated differently, we can mark a method using the @DataProvider annotation on top of the method. According to the code, this is going to mark a method as the data supplier for a test method. Maybe the method is where you load a file or database.

The first attribute is called name which is the name assigned to the data provider method. It has another attribute called parallel which indicates if the data provider should be run in parallel. The last attribute is the indices which indicate which indices should be executed from the data provider.

In conclusion, this is going to make Java threat a method annotated with Data Provider as the source for data for a test and we can do invoke this method by the given name. And is going to run the number of times given in the indices attribute or the number of objects in the return object.

Example:

import org.testng.annotations.DataProvider;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class DP
{
 @DataProvider (name = "data-provider")
 public Object[][] dpMethod(){
 return new Object[][]
 }

    @Test (dataProvider = "data-provider")
    public void myTest (String val) {
        System.out.println("Passed Parameter Is : " + val);
    }
}
import org.testng.annotations.DataProvider;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class DP
{
 @DataProvider (name = "data-provider")
 public Object[][] dpMethod(){
 return new Object[][], {"Second-Value"}};
 }

    @Test (dataProvider = "data-provider")
    public void myTest (String val) {
        System.out.println("Passed Parameter Is : " + val);
    }
}

Due to the indices in this case the test is going to run 2 times.

Factory

Now let's see the factory code.

package org.testng.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * Marks a method as a factory that returns objects that will be used by TestNG as Test classes. The
 * method must return Object[].
 */
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR})
@Documented
public @interface Factory {
  /**
   * The name of the data provider for this test method.
   *
   * @return the data provider name (default none)
   * @see org.testng.annotations.DataProvider
   */
  String dataProvider() default "";

  /**
   * The class where to look for the data provider. If not specified, the dataprovider will be
   * looked on the class of the current test method or one of its super classes. If this attribute
   * is specified, the data provider method needs to be static on the specified class.
   *
   * @return the data provider class (default none)
   */
  Class<?> dataProviderClass() default Object.class;

  /**
   * Whether this factory is enabled.
   *
   * @return the value (default true)
   */
  boolean enabled() default true;

  int[] indices() default {};
}
package org.testng.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * Marks a method as a factory that returns objects that will be used by TestNG as Test classes. The
 * method must return Object[].
 */
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR})
@Documented
public @interface Factory {
  /**
   * The name of the data provider for this test method.
   *
   * @return the data provider name (default none)
   * @see org.testng.annotations.DataProvider
   */
  String dataProvider() default "";

  /**
   * The class where to look for the data provider. If not specified, the dataprovider will be
   * looked on the class of the current test method or one of its super classes. If this attribute
   * is specified, the data provider method needs to be static on the specified class.
   *
   * @return the data provider class (default none)
   */
  Class<?> dataProviderClass() default Object.class;

  /**
   * Whether this factory is enabled.
   *
   * @return the value (default true)
   */
  boolean enabled() default true;

  int[] indices() default {};
}

The first thing to note here is the main description of the annotation. Marks a method as a factory, but the thing to note here is that provides objects, but not. simple objects for testing, it is important to note that, instead it provides objects to be used by TestNG for test classes. We are not returning data for testing nor simple objects, instead, we are going to return objects for testing in simple words, the tests

The attributes of the class, once again a name to identify it. A data provider class where to get the data. Enabled a boolean whether this factory is enabled. Finally the indices once again.

Finally, it has to be used on a method that returns Object[]. Is that, the method has to return test objects, marked with the @Test annotation, we have to save in the Object[] array the tests and the @Factory is going to cover that method into a factory. Why would we use this when you want to run multiple test classes through a single test class.

Example:

package com.journaldev.utils;

import org.testng.annotations.Test;

public class Test1 {

    @Test
    public void test1() {
        System.out.println("Test1 test method");
    }
}



package com.journaldev.utils;

import org.testng.annotations.Test;

public class Test2 {

    @Test
    public void test2() {
        System.out.println("Test2 test method");
    }
}

package com.journaldev.utils;

import org.testng.annotations.Factory;

public class TestNGFactory {

    @Factory()
    public Object[] getTestClasses() {
        Object[] tests = new Object[2];
        tests[0] = new Test1();
        tests[1] = new Test2();
        return tests;
    }

}
package com.journaldev.utils;

import org.testng.annotations.Test;

public class Test1 {

    @Test
    public void test1() {
        System.out.println("Test1 test method");
    }
}



package com.journaldev.utils;

import org.testng.annotations.Test;

public class Test2 {

    @Test
    public void test2() {
        System.out.println("Test2 test method");
    }
}

package com.journaldev.utils;

import org.testng.annotations.Factory;

public class TestNGFactory {

    @Factory()
    public Object[] getTestClasses() {
        Object[] tests = new Object[2];
        tests[0] = new Test1();
        tests[1] = new Test2();
        return tests;
    }

}

Output

[RemoteTestNG] detected TestNG version 6.14.3
Test1 test method
Test2 test method
PASSED: test1
PASSED: test2

===============================================
    Default test
    Tests run: 2, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================
[RemoteTestNG] detected TestNG version 6.14.3
Test1 test method
Test2 test method
PASSED: test1
PASSED: test2

===============================================
    Default test
    Tests run: 2, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

How multiprocessing package manages streams when calling stdout and stderr and how could I do to not split streams when starting a process.


We need to know how streams work because one of the PRs that I'm working on is having a problem with the output and needs a special solution for that. Once again I know nothing so let's try to go step by step. First of all, let's see what is the process class, stdout, and stderr.

Process class

In multiprocessing, processes are spawned by creating a Process object and then calling its start() method.

Process follows the API of threading.Thread. A trivial example of a multiprocess program is

from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

Process objects represent activity that is run in a separate process. The Process class has equivalents of all the methods of threading.

class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
 >>> import multiprocessing, time, signal
 >>> p = multiprocessing.Process(target=time.sleep, args=(1000,))
 >>> print(p, p.is_alive())
 <Process ... initial> False
 >>> p.start()
 >>> print(p, p.is_alive())
 <Process ... started> True
 >>> p.terminate()
 >>> time.sleep(0.1)
 >>> print(p, p.is_alive())
 <Process ... stopped exitcode=-SIGTERM> False
 >>> p.exitcode == -signal.SIGTERM
 True
 >>> import multiprocessing, time, signal
 >>> p = multiprocessing.Process(target=time.sleep, args=(1000,))
 >>> print(p, p.is_alive())
 <Process ... initial> False
 >>> p.start()
 >>> print(p, p.is_alive())
 <Process ... started> True
 >>> p.terminate()
 >>> time.sleep(0.1)
 >>> print(p, p.is_alive())
 <Process ... stopped exitcode=-SIGTERM> False
 >>> p.exitcode == -signal.SIGTERM
 True

Now let's see what is stdout and stderr.

stdout and stderr

sys.stdin
sys.stdout
sys.stderr
sys.stdin
sys.stdout
sys.stderr

There are just output streams... so now knowing this we can see the problem that we are trying to solve.

Problem

Current implementation of [[ShellCommand]] splits stdout and stderr. In our project, we value chronological ordering more than stderr coloring. That's why we wanted to keep stdout and stderr in a single stream (as bash would do by default). You already have want_stdout and want_stderr options, so you offer some configuration in this reagard. Please implement a new option named e.g. merge_stderr or merge_streams which would not split streams when starting the process.

Captura de Pantalla 2021-06-17 a la(s) 18.24.55.png

As we can see these are options or parameters and we need ot create a new one.

@util.deferredLocked('loglock')
   def addStdout(self, data):
       if self.collectStdout:
           self.stdout += data
       if self.stdioLogName is not None and self.stdioLogName in self.logs:
           self.logs[self.stdioLogName].addStdout(data)
       return defer.succeed(None)

   @util.deferredLocked('loglock')
   def addStderr(self, data):
       if self.collectStderr:
           self.stderr += data
       if self.stdioLogName is not None and self.stdioLogName in self.logs:
           self.logs[self.stdioLogName].addStderr(data)
       return defer.succeed(None)
@util.deferredLocked('loglock')
   def addStdout(self, data):
       if self.collectStdout:
           self.stdout += data
       if self.stdioLogName is not None and self.stdioLogName in self.logs:
           self.logs[self.stdioLogName].addStdout(data)
       return defer.succeed(None)

   @util.deferredLocked('loglock')
   def addStderr(self, data):
       if self.collectStderr:
           self.stderr += data
       if self.stdioLogName is not None and self.stdioLogName in self.logs:
           self.logs[self.stdioLogName].addStderr(data)
       return defer.succeed(None)

As we can see here there are two different processes for running the stdout and the stderr, and we want to avoid that.

How can we achieve that? Let's research that.

Subprocess class

The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several older modules and functions:

os.system
os.spawn*
os.system
os.spawn*

I was researching this class when I found that we can do achieve this with this class:

If capture_output is true, stdout and stderr will be captured. When used, the internal Popen object is automatically created with stdout=PIPE and stderr=PIPE. The stdout and stderr arguments may not be supplied at the same time as capture_output. If you wish to capture and combine both streams into one, use stdout=PIPE and stderr=STDOUT instead of capture_output.

subprocess.PIPE
Special value that can be used as the stdin, stdout or stderr argument to Popen and indicates that a pipe to the standard stream should be opened. Most useful with Popen.communicate().
subprocess.PIPE
Special value that can be used as the stdin, stdout or stderr argument to Popen and indicates that a pipe to the standard stream should be opened. Most useful with Popen.communicate().

So now I think that takes 1 line to do the that:

subprocess.call(args, stderr=subprocess.STDOUT)
subprocess.call(args, stderr=subprocess.STDOUT)

This doesn't mean that the problem is solved, not we have to create an option and then test and see if this is really happening. But let's make sure at looking at the code of call.

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs)
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs)

Run the command described by args. Wait for command to complete, then return the returncode attribute.

Now let's see subprocess.STDOUT

stdout
Captured stdout from the child process. A bytes sequence, or a string if run() was called with an encoding, errors, or text=True. None if stdout was not captured.

If you ran the process with stderr=subprocess.STDOUT, stdout and stderr will be combined in this attribute, and stderr will be None.

Yeah maybe that's the solution we can have some conclusions.

Sometimes in systems, you have to decide, it's a trade-off between functionality and sometimes usability, in this case, it is crucial for the system to show the stdout and stderr in the stream because is more important the time than the visual color view.



Lastly the new technichal stuff that I learned.


Technichal new stuff


how
how how how how how how how how

That's all... was a long week... let's keep going.

how