BACNet javascript Injection -Persistent XSS in BACNet devices CVE-2019–7408

Santiago Chile Feb 2019

Bertin b @bertinjoseb

This is about how you can inject javascript code using the bacnet industrial protocol in order to execute code in browser, basically a persistent XSS , and it’s easy as sounds, no bullshit, let’s do this !

The BACNet protocol is UDP based and contains 3 main headers the BVLC , NPDU and APDU the interesting thing here is that BACNet has read and write capability to modify data in the BACNet data structure running usually in building automation devices like controllers protocol converters etc etc .

Usually those devices comes with a web server running in popular ports in order to monitor , perform configuration changes , read data and other stuff.

The web application typically use the BACNet data structure in order to print information in the web application about the BACNet device status/name/location and other settings.

The thing is , if you can modify the string data about location or device name from the BACNet protocol itself, there is a possibility to inject javascript code instead of the regular strings values for those tags elements.

The cool thing here is you’re going to trigger a stored XSS without touching the web application at all, for this attack/technique you just need to understand the BACNet protocol fundamentals and have access to the specific scenario with all requirements in order to trigger the stored XSS using the BACNet write properties.

Important things you need to consider in order recreate this scenario :

  • A BACNet port open UDP 47808
  • A web application printing BACNet data ,sometimes you have Bacnet open port but no web app :-(
  • Web application research, sometimes you need to break tags in order to trigger the XSS (you know what i mean….).
  • Write capability in location and device name, sometimes there is no write permissions

With all that explained you’re ready to go, for this time i will share a exotic vulnerable device connected to the internet that i found with all requirements explained previously , the first time i try this technique was in demo environment, this time is a real device, s**t got real.

Our lab :

Fire up your favorite search engine

In my case zoomeye works fine

Looks for “pCOBCM@”, zoomeye currently is indexing port 47808 and also retrieving the bacnet data .

This is a McQuay device, “bacnet controller”, related to chillers and stuff like that.

As you can see there is nothing in the web application, just visually , i mean, there is no access at all , there is no info but…..check the source code and you will find gold .

Well, ignore the reset password, is not related to our purposes today ,was just fun , but as you can see our target is running bacnet and also is printing bacnet sysinfo string values , in theory properties of the device object in bacnet .

Our goal is replace the string “unknow” for javascript code without touching or have access to the web application , let’s see how to do that , so for now hold on the device for a while , let’s dig into bacnet protocol.

BACNet Fundamentals

In order to write values you must know first how to read them and how bacnet works.

So, it’s really important to read the BACNet documentation in order to understand how the protocol works and all that stuff, as i said before there are 3 main headers , the following could become complex in terms of the protocol implementation and development and could mislead the perspective of readers / BACNet experts and the people who wants to understand how the exploit works , i will try to cover the most relevant parts and theory of the protocol in order to exploit the web application , so many things are going to be straight forward and in case you want to explore more in depth i think this is a good point to start.

The firts one is called : BVLC (BACNet virtual link control) , basically 4 bytes

The firts byte defines the type , in this case bacnet/ip 0x81 , the second one defines the function 0x0a , and the last 2 bytes defines the length of the whole packet, for this example our packet is 17 bytes.

The second header is called NPDU (Network layer protocol data unit)

According bacnetwiki this is the representation, but be careful here, in practice the headers is just 2 bytes, the version and the control byte

The last header is the APDU , BACnet APDUs carry the Application Layer parameters with a variable length, the length of the payload also must be specified , be careful with that , otherwise you will face malformed packet issues .

The magic is in the APDU header that allows you to read or WRITE values in the BACNet data structure.

Bacnet service choice

In order to read / write you need to set up correctly the service choice byte, at this point probably you’re taking a look to the 0x14 byte (reinitialize device) but we will cover that other day , so in order to read would be 0x0c , for write would be 0x0f .

In order to read , to need to use the byte 0x0c (12 Dec)

Read property 0x0c (12)

The values that we need to read and write will be the BACNet objects.

BACNet Object (bacnetwiki):

Unique in a given device. Not necessarily internetwork wide. The Object Identifier of an object in combination with the Object Identifier of the Device is what makes an object unique. Value of 4194303 is not allowed. A value of 4194303 used anywhere as an Object Identifier means that the containing object is not initialized.

The object identifier contains the property identifier , basically the string values representing human readable strings like location, device model, device name and so on, in few words ..our targets today.

There are several tools available you can use in order to read and write with BACNet, but the purpose of this post is go in depth and interact with the device using no tools. Any language that feel comfortable is useful in this scenario, python or C helps a lot.

Now we know the components and how the read property works, let’s try our first attempt to read some BACNet string values, in order to do that we can go back to our BACNet device from zoomeye.

The byte structure in order to read BACNet values (object identifier)would be the following :

Why the object identifier ? well.. because in order to write later you will need to object identifier .

Let’s check the response , if everything is fine at this point you will notice the communication with the BACNet device is successful , also the response back contains the specific value for the object identifier , keep this value in mind, we will use it later in order to write.

Now we know how to read specific values form the BACNet data structure, but the cool thing is that we can modify those values, and also the same values are being taken from the application to represent sting values, in theory, if we modify the string values for Javscript code instead we could be able execute Javscript in browser.

Write property

The write property is tricky, sometime some objects are restricted to be modified, but most the times the property value location is able to be modified from the bacnet protocol. Also in order to write you need the device id (mandatory), remember that one in the read packet above? yes that one.

Now we know how to read and also how to write , let’s inject our javascript payload abusing the BACNet write capability , but first let’s go back to our BACNet device web application and take a look

Quick recap

The application is taking values from BACNet to be represented as string values in the web front end, those values can be modified from the BACNet data structure using the BACNet protocol write capabilities , in good theory if i replace the string values for javscript code , the javascript code will be executed in browser because is a valid piece of code in the web application

Open up the source code ,check again the value of the location tag in the device, our goal is the following : the string “Unknown” will be replaced for JS code, the result will be a stored XSS in the device web application , in <td></td> there is no need for special XSS payload, a single alert in script tags is enough to trigger the XSS.

There is no need to explain again the headers, at this point you should understand how to write , the documentation explains pretty well, there are only few changes in the APDU header like the length and our javascript payload in location tag , remember that our goal is modify the location tag, i know there are other attacks that could be possible hidden in the BACNet surface but today our goal is a Stored XSS, i’m still playing around with this protocol and more research is going to be published soon.

This is an example about how the write packet should looks like, you clearly see the payload in location property and the service choice is set to 15 , which means writeproperty , if everything goes well the server should reply with an simple ACK like this one :

The payload has been wrote successfully in the BACNet data structure, in good theory now our payload should be executed in the browser .



The technique allows you to inject javascript without touching the web application at all .

This a javascript protocol based injection technique

in this particular scenario we don’t need credentials or access to the web application at all , just inject the payload and say goodbye.

Several protocols are also affected by this kind of vector, example : SNMP.

Several BACNet devices with the port 47808 are vulnerable to be modified remotely using the BACnet protocol .

The Number of current BACnet devices affected by this technique is unknow at least for me.

Close your BACNet port 47808 in the internet otherwise i will inject you stuff… or others actors.

There are several tools to read and write, i decided to explains in depth becacuse we need to understand how exactly the BACnet protocol works.

If you experience issues trying to reproduce the vulnerability this way , check your packet lengths, or operation codes.

 Bertin Bervis

Use CVE-2019–7408