Today we will discuss some basic security aspects of database user interfaces. The principles are not at all restricted to PicoLisp, it's a general (and quite wide-spread) vulnerability: Cross-Site Scripting.
What are Cross-Site Scripting attacks?
XSS vulnerabilities are one of the most common flaws in web applications - Top 3 according to the Open Web Application Security Project (OWASP) after "Broken Access Control" and "Cryptographic Failures".
Instead of theory, let's start with an example. Actually we have introduced a great example for XSS vulnerability in the post "Creating HTML Forms 2". (Did you notice?)
If you check the source code, this is basically what we do: We save the user input in two global variables
*LastName and output it via
(prinl "Hello, " *FirstName " " *LastName "!")
prinl takes any kind of expression without modifications. Let's see what that means!
You can try it yourself here.
Ctrl-U in Firefox).
<input type="text" name="*FirstName" value="<script>alert("You've been hacked!")</script>" size="30" onchange="return fldChg(this)" class="field"/>
<h3> tag below is not:
<h3 class="d-flex m-1 justify-content-center">Hello, <script>alert("You've been hacked!")</script> B!</h3>
So let's now take a look at a dangerous one: stored XSS attacks.
Stored XSS Attacks
An XSS-Attack is called stored if the input is saved somewhere in the backend, for example in the database. In this case not only the attacker, but everyone viewing the page is potentially affected. In order to make that possible, two vulnerabilities must occur at the same time:
- The input data is directly written into the database without checks, and
- The input data is directly evaluated in the code.
Let's take a look at the following, vulnerable version of our
family-edit.l program. It is identical to the previous line except for one additional line:
(<p> NIL (prin (get *ID 'job) ", born " (datStr(get *ID 'dat))))
The programmer wanted to create a subtitle that prints the occupation and birthday of the record. In rendered form, it looks like this:
Unfortunately, with this line he or she introduced a serious vulnerability. Let's see what kind of fun an attacker can have with this.
You can download the vulnerable program here. The database is expected in the folder
family-vulnerable/, I recommend to download the
family/ database and copy it in a new folder, so that the original one doesn't get corrupted.
Proof of Concept
Differently to before, every user who is now accesing the database will see You've been hacked instead of the user record first. This alone can already be a problem, but there are even worse things that an attacker can do with that.
Corrupting the date field
But before we get into that, let's try the same with our date string. When we try the same steps as before, we see that we cannot change it - the submission is blocked with the notification "Bad date format".
Does that mean we're safe? Maybe not, because the check is only on front-end side. Let's try to catch the POST request and modify it before sending it to the server.
I have opened a virtual Kali Linux machine on my system to access the web app on the host system. Using Burp Suite as proxy, let's see what our app is sending. Let's modify the date and catch the request in our proxy.
Now we modify this value to
<script>alert("You've been hacked!")</script> before we forward it.
However, when we try to send it, we get a "server connect failure". Why? Maybe the interpreter threw an error because this evaluation was not possible anymore:
(datStr(get *ID 'dat))
So we could try to inject
"1926-04-23" ) "hello world". This should render to something like this:
(datStr "1926-04-23" ) "hello world"
Unfortunately (or rather luckily), this also fails, because date is transformed to a timestamp in the backend, which fails if we send anything except data strings. Similarly, comments,
NIL and empty strings are rejected.
So I assume the
date fields cannot be injected so easily since the date conversion is acting as data sanitizer.
Nevertheless, we still have the
job property where we can execute our malicious scripts. For example, we could try to get the session ID of anybody who is opening the database. In order to get these, we need the application to connect back to us.
Instead of adding a simple alert, let's do this:
Attacker_IP by your own (local) IP.
Then we open a port on the attacking system (for example with netcat) As soon as the victim opens the corrupted database, the page tries to load a "picture" from the attacker's computer. Of course it fails because there is no such picture, but from the request, the attacker can get the complete session URL, and if available, also the session authentication cookies.
In other words, the attacker can now use the session of another user! This could be a big problem, right? Let's assume the administrator has logged in, and we can steal session URL and cookies (if available) from that person. This means that the attacker could execute commands with administrator rights, for example download the whole database, or even take over the control over the server!
What do we learn from that?
XSS attacks can be dangerous, especially in combination with a storage mechanism on the backend side. As you can see, the attacker doesn't even have to know anything about PicoLisp, and this kind of attack is also very easy to automate, so the probability is quite high that it will be discovered.
What can we do to prevent it?
- Try to use standard framework components where possible. For example, the GUI elements of the PicoLisp GUI frameworks are safe to use since they remove special characters before displaying.
- Always specify and filter the user input, don't commit anything unchecked into the database. Sanitize the data if possible.
- Never directly evaluate user input data (for example: use
prinif the content is not known beforehand).
- Consider to use scanning tools to double-check your application.
The good news is: XSS exploits are easy to exploit, but also quite easy to prevent. Always follow these two rules and your application should be safe!