Forging SWIFT MT Payment Messages for fun and pr... research!

By Oliver Simonnet on 7 February, 2020

Oliver Simonnet

7 February, 2020

TLDR: With a bit of research and support we were able to demonstrate a proof of concept for introducing a fraudulent payment message to move £0.5M from one account to another, by manually forging a raw SWIFT MT103 message, and leveraging specific system trust relationships to do the hard work for us!


Before we begin: This research is based on work we performed in close-collaboration with one of our clients; however, the systems, architecture, and payment-related details have been generalized / redacted / modified as to not disclose information specific to their environment.

With that said... *clears throat*

The typical Tactics, Techniques and Procedures (TTPs) against SWIFT systems we see in reports and the media are - for the most part - the following:

  1. Compromise the institution's network;
  2. Move laterally towards critical payment systems;
  3. Compromise multiple SWIFT Payment Operator (PO) credentials;
  4. Access the institution's SWIFT Messaging Interface (MI);
  5. Keys in - and then authorize - payment messages using the compromised PO accounts on the MI.

This attack-path requires the compromise of multiple users, multiple systems, an understanding of how to use the target application, bypass of 2FA, attempts to hide access logs, avoid alerting the legitimate operators, attempts to disrupt physical evidence, bespoke malware, etc. – so, quite involved and difficult. Now that’s all good and fine, but having reviewed a few different payment system architectures over the years, I can’t help but wonder:

“Can't an attacker just target the system at a lower level? Why not target the Message Queues directly? Can it be done?”

Well, let's find out! My mission begins!

Phase 1 - Understand the Overall System!

So, first things first! I needed to fully understand the specific “section” of the target institution's payment landscape I was going to focus on for this research. In this narrative, there will be a system called “Payment System” (SYS). This system is part of the institution's back-office payment landscape, receiving data in a custom format and output's an initial payment instructions in ISO 15022 / RJE / SWIFT MT format.  The reason I sought this scenario was specifically because I wanted to focus on attempting to forge an MT103 payment message - that is:

  • MT – “Message Type” Literal;
  • 1 – Category 1 (Customer Payments and Cheques);
  • 0 – Group 0 (Financial Institution Transfer);
  • 3 – Type 3 (Notification);
  • All together this is classified as the MT103 “Single Customer Credit Transfer”.

Message type aside, what does this payment flow look like at a high level? Well I’ve only gone and made a fancy diagram for this!

Overall this is a very typical and generic architecture design. However, let me roughly break down what this does:

  1. The Payment System (SYS) ingests data in a custom - or alternative - message format from it's respective upstream systems. SYS then outputs an initial payment instruction in SWIFT MT format;
  2. SYS sends this initial message downstream to a shared middelware (MID) component, which converts (if necessary) the received message into the modern MT format understood by SWIFT - Essentially a message broker used by a range of upstream payment systems within the institution;
  3. MID forwards the message in it's new format on to the institution's Messaging Interface (let's say its SAA in this instance) for processing;
  4. Once received by SAA, the message content is read by the institution's sanction screening / Anti-money laundering systems (SANCT).
  5. Given no issues are found, the message is sent on to the institution's Communication Interface (SWIFT Alliance Gateway), where it's then signed and routed to the recipient institution over SWIFTNet.

OK, so now I have a general understanding of what I'm up against. But if I wanted to exploit the relationships between these systems to introduce a  fraudulent payment without targeting any payment operators, I was going to need to dig deeper and understand the fundamental technologies in use!

Phase 2 – Understand the Technologies

So how are these messages actually "passed" between each system? I need to know exactly what this looks like and how its done!

More often than not, Message Queues (MQ) are heavily used to pass messages between components in a large payment system. However, there are also various “Adapter” that may be used between systems communicating directly with the SAG (Such as SAA or other bespoke/3rd party systems). These are typically the:

  • Remote API Host Adapter (RAHA);
  • MQ Host Adapter (MQHA);
  • Web Services Host Adapter (WSHA).

Having identified that MQ was in use, my initial assumption was that there was most likely a dedicated Queue Manager (QM) server somewhere hosting various queues that systems push and pull messages from? However, due to SWIFT CSP requirements, this would most likely - at a minimum - take the form of two Queue Managers. One which manages the queues within the SWIFT Secure Zone, and another that manages queues for the general corporate network and back office systems.

Let's update that diagram to track / represent this understanding:

Now I could research how this "messaging" worked!

There are multiple ways to configure Message Queues architectures, in this case there were various dedicated input and output queues for each system, and the message flow looks something like this:

Full disclosure, turns out it’s hard to draw an accurate - yet simple - MQ flow diagram (that one was basically my 4th attempt). So it’s... accurate "enough" for what we needed to remember!

Phase 3 – Defining an Attack

Now I had a good understanding of how it all worked, it is time to define my goal: "Place a payment message directly on to a queue, and have it successfully processed by all downstream systems".

This sounds simple, just write a message to a queue, right? But there are a few complications!

  1. Why are there few indications of this attack vector in the wild?
  2. How do I even gain “write” access to the right queue?
  3. What protects the message on the queues?
  4. What protects the messages in transit?
  5. What format are the messages in?
  6. What is the correct syntax for that message format at any particular queue (0 margin for error)?
  7. Where does PKI come in? How / where / when are the messages signed?
  8. Can I somehow get around the message signing?
  9. What values in the messages are dependent / controlled / defined by the system processing them (out of my control)?
  10. What is the maximum amount I can transfer using Straight Through Processing, without alerting the institution / requiring manual validation?

But OK, there's no point dwelling on all of that right now, I'll just clearly define what I want to do!

The goal:

  1. Successfully write a payment instruction for 500,000 GBP;
  2. Inject that message directly onto a specific queue;
  3. Have the message pass environment-specific validation rules;
  4. Have the message pass sanctions and AML checks.
  5. Have the message successfully signed;
  6. Have the message pass SWIFTNet-specific validation rules;

What I was not interested in doing for this research - yet needed to understand nevertheless for a full attack chain was:

  1. How to compromise the institution's network;
  2. How to gain access to the MQ admin's workstation;
  3. How to obtain the pre-requisite credentials.

What I wanted to 100% avoid at all costs:

  1. The attack involving SWIFT payment operators in any way;
  2. The attack involving SWIFT application access in any way;
  3. A need to compromise signing keys / HSMs;
  4. A need to compromise SWIFTNet operator accounts or certificates or any type of PKI;.

Phase 3 – Understanding the MT103 Message Body Format

Now I had an idea of what to do, I needed to make sure I could write a raw MT103 payment instruction! Typically, even when operators write payment messages using a messaging interface application like Alliance Access, they only really write the message “body” via a nice GUI. As raw data this could look something like:


I'll break this down in the following table:

Name Field Value
Transaction Reference 20 TRANSACTIONRF103
Bank Operation Code 23B CRED (Message is to "credit" some beneficiary)
Value Date / Currency / Amount 32A 200102 (02/01/2020) GBP 500,000.00
Currency / Original Credit Amount 33B GBP 500000,00 (£500,000.00)
Ordering Customer 50K GB22EBNK88227712345678 (IBAN)
Beneficiary 59K FR20FBNK88332287654321 (IBAN)
Remittance Information 70 12345-67890 (essentially a payment reference)
Details of Charge 71A SHA (Shared charge between sender and receiver)

Now as this is a valid message body, if I were targeting a payment operator on SWIFT Alliance Access, I could - for the "most" part - simply paste the message into SAA's raw message creation interface and I'd be pretty much done. With the exception of adding the sender / recipient BIC codes and most likely selecting a business unit. However, these values are not stored in the message body.  

Not stored in the message body you say? Well that complicates things! Where are they stored exactly?

Phase 4 – Understanding the full MT103 Message Format

The message “body” is referred to as “block 4” (aka the “Text Block”) within the SWIFT MT standard. As suggested by the name, there is probably also a block 1-3. This is correct; and these blocks are typically generated by the payment processing applications - such as SWIFT Alliance Access - and not necessarily input by the operators. A "complete" MT103 message consists of 6 blocks:

  • Block 1 – Basic Header
  • Block 2 – Application Header
  • Block 3 – User Header
  • Block 4 – Text Block
  • Block 5 – Trailer
  • Block 6 – System block

So it looked like I was going to need to learn how to craft these various “blocks” from scratch.

Block 1 (Basic header)

Reading through some documentation, I crafted the following “Basic header” block:


A breakdown of what this translates too is as follows:

Name Value Context
Basic Header Flag 1 Block 1 (Not 2, 3, 4, or 5)
Application Type F FIN Application
Message Type 01 01 = FIN (I.e not ACK/NACK)
Sender BIC EBNKGB20 EBNK (Bank Code) GB (Country Code) 20 (Location Code)
Sender Logical Terminal A Typically A, unless they are a significantly large institution and require multiple terminals
Sender Branch XXX All X if no branch needed
Session Number 0000 The session number for the message
Sequence Number  999999 The sequence number of the message

Taking a step back, I already identified two potential problems: the “session” and “sequence” numbers! These are described as follows:

  • Session Number – Must also equal the current application session number of the application entity that receives the input message.
  • Sequence number – The sequence number must be equal to the next expected number.

Hmmm, at this point I was not sure how I could predetermine a valid session and/or sequence number - considering they seemed to be application and "traffic" specific? But there was nothing I could do at the time, so I noted it down in a list of "issues/blockers" to come back to later.

Block 2 (Application Header)

A bit more dry reading later, I managed to also throw together an application header:


Again, I’ve broken this down so it makes sense (if it didn’t already; I’m not one to assume):

Name Value Context
Application Header Flag 2 Block 2
I/O Identifier I Input Message (a message being sent)
Message Type 103 103 = Single Customer Credit Transaction
Recipient BIC FBNKFR20 FBNK (Bank Code) FR (Country Code) 20 (Location Code)
Recipient Logical Terminal X All General Purpose Application Messages must use "X"
Recipient Branch XXX All General Purpose Application Messages must use "XXX"
Message Priority N Normal (Not Urgent)

Awesome! No issues crafting this header!

Note: At this point I should probably mention that these BIC codes are not "real", however are accurate in terms of in format and length.

Block 3 (User Header)

The third block is called the “User Header” block, which can be used to define some “special” processing rules. By leverage this header, I could specify that the message should be processed using “Straight Through Processing” (STP) rules which essentially attempts to ensure that the message is processed end-to-end without human intervention. This could be specified as follows:


However, this was not yet a valid header! As of November 2018 the user header requires a mandatory “Unique end-to-end transaction reference” (UETR) value, which was introduced as part of SWIFT's Global Payments Innovation initiative (gpi)!

This is a Globally Unique Identifier (GUID) compliant with the 4th version of the generation algorithm used by the IETF standard "RFC4122". This consists of 32 hexadecimal characters, divided into 5 parts by hyphens as follows:



  • x – any lowercase hexadecimal character;
  • 4 – fixed value;
  • y – either: 8, 9, a, b.

This value can be generated using Python as seen below:

$ python -c 'import uuid; id = uuid.uuid4(); print "Value:", id; print "Version:", id.version, id.variant'
Value: 8b1b42b5-669f-46ff-b2f2-c21f99788834
Version: 4 specified in RFC 4122

With an acceptable UETR generated, this is how the third block looked:


And as before, a breakdown can be found below:

Name Value Context
User Header Flag 3 Block 3
Validation Flag 119 Indicates whether FIN must perform any type of special validation
Validation Field STP Requests the FIN system to validate the message according to the straight through processing principles
UETR Field 121 Indicates the Unique end-to-end transaction reference value
UETR Value 8b1b42b5-669f-46ff-b2f2-c21f99788834 Unique end-to-end transaction reference used to track payment instruction

Block 5 and 6 (Trailer and System Blocks)

I’ve already discussed “block 4” (the message body), so to wrap this section up, I'll be looking at the final 2 blocks: Block 5, aka the “Trailer”; and block S, aka the “System” block.

Before going forward, let me take a moment to explain the pointlessly complicated concept of input and output messages:

  • An “input” message (I) is a message which is traveling “outbound” from the institution. So this is a message being “input” by an operator and sent by the institution to another institution.
  • An “output” message (O) is a message which is traveling “inbound” to the institution. So this is a message being “output” by SWIFTNet and being received by the institution.

OK, moving swiftly (aaaahhhhh!) on.

For Input messages, these blocks were not too much of a problem. The headers only really seemed to be used to flag whether the message was for training / testing or to flag if it was a possible duplicate, which syntactically took the following form:


Where “TNG” indicated “training” and “SPD” indicated “possible duplicate”.

However, with Output messages, it got considerably more complicated. An example of what the trailer and system block could look like on an Output message is the following:


A breakdown of these various values is:

Trailer ({5:)
MAC – Message Authentication Code calculated based on the entire contents of the message using a key that has been exchanged with the destination bank and a secret algorithm;
CHK – This is a PKI checksum of the message body, used to ensure the message has not been corrupted in transit;
TNG – A flag to indicate that the message is a Testing and Training Message.

System ({S:)
SPD – Possible Duplicate Flag
SAC – Successfully Authenticated and Authorized Flag. This is only present if:

  1. Signature verification was successful.
  2. RMA (Relationship Management Application) authorization and verification was successful.

COP – Flag indicating that this is the primary message copy;
MDG – The HMAC256 of the message using LAU keys.

However, these seemed to only be values I would need to consider if I was to try and forge an “incoming” message from SWIFTNet or an "outbound" message on the output of the SAG.

So... I'll stick with crafting an “input" message trailer:


Now, having said all that, it turned out the trailer block did seem to sometimes hold a MAC code and a message checksum (sigh), meaning I actually needed to construct something like:


So that was +2 to my "issues/blockers" list. However, issues aside, I now understood the complete message format, and could put it all together and save the following as a draft / template MT103 message:


Highlighted in bold above are the areas of the message I was - at this point - unable to pre-determine. Nevertheless, a summary of what that the message describes is:

  • Using the transaction reference “TRANSACTIONRF103”;
  • please transfer 500,000.00 GBP;
  • from John Doe, (IBAN: GB22EBNK88227712345678) at “English Bank” (BIC: EBNKGB20);
  • to Alice Smith (IBAN: FR20FBNK88332287654321) at “French Bank” (BIC: FBNKFR20);
  • Furthermore, please ensure the transaction charge is shared between the two institutions;
  • and mark the payment with a reference of “12345-67890”.

To wrap up this section, i wanted to take a moment to explain some logic behind the target of 500,000 GBP, as it is also important.

Aside from the many reasons it would be better to transfer [even] smaller amounts (which is an increasingly common tactic deployed by modern threat actors), why not go higher? This is where it’s important to understand the system and environment you are targeting.

In this instance, let's assume that by doing recon for a while I gathered the understanding that:

  • If a message comes from SYS which is over £500k;
  • even if it has been subject to a 4 eye check;
  • and even if it is flagged for STP processing;
  • route it to a verification queue and hold it for manual verification.

This was because a transaction over £500k was determined to be “abnormal” for SYS. As such, if my transaction was greater, the message would not propagate through all systems automatically.

OK, so now that I understood:

  • how the system worked;
  • how it communicated;
  • the fundamental structure of a raw MT103 payment messages;
  • and how much I could reliably [attempt] to transfer.

And with that, it was time to take a break from MT standards and establish an understanding of how I would even get into a position to put this into practice!

Phase 5 - Getting into Position

To place a message on a queue, I was going to need two things:

  • Access to the correct queue manager;
  • Write access to the correct queues.

Depending on the environment and organisation, access to queue managers could be quite different and complex. However a bare-bones setup may take the following form:

  • An MQ Administrator accesses their dedicated workstation using AD credentials;
  • They then remotely access a dedicated jump server via RDP which only their host is whitelisted to access;
  • This may be required as the queues may make use of Channel Authentication Records, authorizing specific systems and user accounts access to specific queues;
  • The channels may further be protected by MQ Message Encryption (MQME) which encrypts messages at rest based on specific channels. As such, even if someone was a “super duper master admin” they would only be able to read / write to queues specifically allocated to them within the MQME configuration file (potential target for another time?);
  • The MQ Admin can then use tools such via the Jump Server to read/write to their desired message queues.

So, in this scenario, to gain access to the message queues I - as an attacker - would need to compromise the MQ admin’s AD account and workstations, then use this to gain access to the jump host, from where I could then access the message queues given I knew the correct channel name and was configured with authorization to access it... and maybe throw some MFA in there...

That is understandably a significant requirement! However, when discussion sophisticated attacks against Financial Market Infrastructure (FMI), it is more than reasonable to accept that an Advanced Persistent Threat (APT) would see this as a feasible objective - We don't need to dig into the history of how sophisticated attacks targeting SWIFT systems can be.

Next, it was time to finally identify a feasible attack vector for message forgery.

Phase 6 – Identifying a Viable Attack Vector

Now with an idea of how to gain the right access, as well as an understanding of the various technologies and security controls in place; I update my diagram:

You may have noticed I've added something called “LAU” around the SAA-to-SAG adapter, and another “LAU” to the MID-to-SAA MQ channels, which I have yet to explain.  “Local Authentication” (LAU) is a security control implemented by SWIFT to authenticate messages using a pair of shared keys between two systems. These keys are combined and used to generate a SHA256 HMAC of the message and append it to the S block. This can then be validated by the recipient system. Effectively, this validates the origin and authenticity of a message. As such, even if an attacker was in position to introduce a fraudulent payment, they'd first need to compromise both the left and the right LAU signing keys, generate the correct HMAC, and append it to the message in order to have it accepted / processed successfully.

But LAU aside, I now just needed to figure out which queue to target! There were a lot of queues to work with as each system essentially has multiple “input” and “output” queues. With that in mind, it was important to note that: an incoming message would require being in the format expected by the target system (from a specific upstream system) and an outgoing message would need to be in the format “produced” by one target system and “expected / ingested / processed” by its respective downstream system. So to figure this out, I worked backwards from the Gateway.

Targeting SAG

This was the least feasible attack vector!

  1. I hadn't really looked into how the SWIFT adapters worked - If only I could research literally everything);
  2. SAA and SAG implemented LAU on messages sent between them - An excellent security control!;
  3. The output of SAG was directly on to SWIFTNet which would entail all sorts of other complications - this is an understatement)!


Targeting SAA

So what if I wanted to drop a message on the “outbound” channel of SAA?

LAU and the SWIFT adapter aside, remember those session and sequence numbers? Well, messages which leave SAA are in the near-final stages of their outbound life-cycle, and as far as I understood would need to have valid session and sequence values. Given I didn't know how to generate these values without gaining access to SAA or how they worked in general (and lets not forget the LAU signing) this didn't currently seem feasible.


Targeting SANCT

This solution didn't actually transport messages back and forth; it just reads messages off the queues and performed checks on their details. Not much I could wanted to leverage here.

Targeting MID

To target MID, I could try and inject a message onto SAA’s “input” queue, or the “output” queue of MID. This would only need to match the format of messages produced by the Middleware solution (MID). Following this, in theory, the [mistial] message session and sequence number would be added by SAA, along with the UETR. This was promising!

However, MID was a SWIFT “message partner”, which are typically solutions developed using the Alliance Access Development Kit that allows vendors to develop SWIFTNet compatible software, and consequentially, implement LAU. So again, in-order to forge a message here, I’d need to compromise the left and right LAU signing keys used between SAA and MID, manually HMAC the message (correctly!), and then place it on the correct queue... This also no longer looked promising...

Targeting SYS

OK, how about the input of the next system down - the "Payment System"?

As described previously, the inbound data was a custom “application specific” payment instruction from the institutions back office systems, and not a SWIFT MT message. This would be an entirely new core concept I'd need to reverse - not ideal for this project.

But how about the output queue?

Although SYS received custom format data, I found that it output what seemed to be an initial SWIFT MT messages. This was perfect! Additionally, SYS did not have LAU between itself and MID because (unlike MID) SYS was not a SWIFT message partner, and was just one of many-many systems within the institution that formed their overall payment landscape.

Additionally, because SYS was esentially just one small piece of a much larger back office architecture, it was not part of the SWIFT Secure Zone (after all you cant have your entire estate in the Secure Zone - that defeats the purpose) and as such, made use of the Queue Manager within a more accessible section of the general corporate environment (QM1).

With this in mind, and having - in theory - compromised the MQ admin, I could leverage their access to access on the corporate network to authenticate to QM1. I could - in theory -  then write a fraudulent payment message to the SYS “output” queue, which we will call “SYS_PAY_OUT_Q” from here on.

OK! It seems like I finally had an idea of what to do! But before I could put it into practice, I of course needed to create a diagram of the attack:

I think it’s important to take a minute to refer back to the concept of “trust” which is what lead to this attack diagram. My theory behind why this may work is because the MID application, implicitly trusts whatever it receives from its respective upstream systems. This is intentional, as by design the security model of the payment landscape ensures that: at any point a message can be created, a 4 (or 6) eye check is performed. If there was a system whose purpose it was to ensure the validity of a payment message at any point upstream, the downstream systems should have no real issue processing that message (with some exceptions). After all, It would be next to-impossible to maintain a high-throughput payment system without this design.

And with that said, the plan was now clear:

  • Leverage the access of a Message Queue administrator;
  • to abuse the “trust relationship” between SYS, MID, and SAA;
  • to introduce a fraudulent payment message directly on to the output queue of SYS;
  • by leaning on my new found understanding of complete MT103 payment messages.

It was finally time to try to demonstrate a Proof-of-Concept attack!

Phase 7 – PoC Attack Execution!

So at this point I believe I had everything I needed in order to execute the attack:

  • The target system!
  • The message format!
  • The queue manager!
  • The queue!
  • The access requirements!
  • The generously granted access to a fully functional SWIFT messaging architecture! (that’s a good one to have!)
  • The extra-generously granted support of various SMEs from the target institution! (This was even better to have!)

Message Forgery

I needed to begin by creating a valid payment message using valid details from the target institution. So before moving on I was provided with the following (Note: as with many things in this post, these details have been faked):

  • Debtor Account Details – John Doe, GB12EBNK88227712345678 at EBNKGB20
  • Creditor Account Details – Alice Smith, GB15EBNK88332287654321 at EBNKGB20

Some of you may have notice that the sending and receiving BIC’s are the same. This was because, for the sake of the research, I wanted to send the message back to the target institution via SWIFTNet so that I could analyse its full end-to-end message history. Furthermore, you may have noticed we are using "test & training" BIC code (where the 8th character is a 0) - this was to make sure, you know, that I kept my job.

But yes, with access to these "valid" account details and the knowledge gained during the research so far, I could now forge a complete Input MT103 messages:


Note: Field 33B is actually an optional field, however, the MT standard stated that “If the country codes of both the Sender’s and the Receiver’s BIC belong to the country code list, then field 33B is mandatory”. As such, if 33B was not present in the message, it would fail network validation rules and SWIFTNet would return a NAK with the error code: D49.

Optional / Mandatory fields aside, it was not quite that simple! There were a few minor changes I needed to make based on the specific point in the message's its life-cycle I was planning to introduce it!

As I list these changes, remember that the objective is to introduce the message to the output queue of SYS (Which exists before MID, SAA and SAG)

  1. The first 3 blocks needed to be placed on a single line;
  2. Remove field 121 (UETR) from the User Header, as this would be generated by SAA during processing;
  3. Remove 1 character from the transaction reference as it needed to be exactly 16 characters (classic user error);
  4. Add decimal point to transaction amount using a comma - otherwise it would fail syntax validation rules;
  5. Ensure the IBAN's were real and accurate, otherwise it seemed the message would fail some type of signature validation on the SWIFT network. The IBANs are fake here, but during the real PoC we used accurate account details in collaboration with the target institution;
  6. Remove the trailer block (5) - as this would be appended by SAA during processing;
  7. Remove the System Block (S) - as this would be completed by the SAG.

And the final message was as follows:


Note that the location in which I introduce the message has resolved all of the "issues / blockers" I'd tracked whilst researching the message structure! It would seem the further upstream you go, the easier the attack becomes - given MQ is still used as a transport medium.

Message Injection

Now I had my raw MT103 message, I just need to save it to a file (“Message.txt” - sure why not) and place onto the “SYS_PAY_OUT_Q” queue using one of the admin's tools:

  1. With access to a sole MQ Administrator's AD account;
  2. We connect to the MQ admins machine;
  3. Log into the Jump Server;
  4. Open our MQ tools of choice and authenticate to queue manager (QM1) where the output queue for SYS was managed;
  5. Connected to the "SYS_PAY_OUT_Q" queue;
  6. Selected my forged “Message.txt” file;
  7. Invoked the “write to queue” function;

And it was off!

Loggin in to Alliance Access and opening the message history tab, we sat awaiting for an update. Waiting, waiting, waiting… waiting… and...

ACK! It worked!

That's a joke; did we hell receive an ACK!

See, this last section is written slightly more "linear" than what actually happened. Remember those "tweaks" used to fix the message in the previous section? I hadn't quite figured that out yet...

So roughly seven NACKs later - each time troubleshooting and then fixing a different issues - we did indeed, see an ACK! The message was successfully processed by all systems, passed target system validation rules, passed sanctions and AML screening, passed SWIFTNet validation rules, and SWIFT’s regional processor had received the message and sent an "Acknowledgement of receipt" response to the sending institution!

For the sake of completeness, I’ve included the ACK below:


And of course a breakdown of what it all means:

Name Value Context
Basic Header Flag 1 Block 1
Application Type F F = FIN Application
Message Type 21 21 = ACK
Institution Code EBNKGB20AXXX EBNKGB20 (BIC) A (Logical Terminal) XXX (Branch)
Sequence and Session No. 1947392344 1947 (Sequence No.) 392344 (Session No.)
Date Tag 177 200103 (Date) 1102 (Time)
Accept / Reject Tag 451 0 = Accepted by SWIFTNet

Excellent! WooHoo! It worked! ... That took a lot of time and effort!

Closer Inspection

But the ACK wasn't enough, I wanted to make sure I understood what had happened to the message throughout its life-cycle. From the message I placed on the initial queue, to being processed by SWIFTNet.

Thankfully, as we sent the message back to the target institution we could see its entire message history. I already knew what the raw message placed on the queue looked like, so I wanted to focus on what became of the message once it had been processed by SAA:


  • The end-to-end tracking UUID had been generated and added (b42857ce-3931-49bf-ba34-16dd7a0c929f) in block 3;
  • The message trailer had been added ({5:{TNG:}}) where I could see that - due to the BIC code used - SAA had flagged the message as "test and training".
  • Additionally, an initial System Block segment had been added ({S:{SPD:}}), tagging the message as a possible duplicate. I wonder why - *cough* 7th attempt *cough*?

OK, so that was SAA. Now let’s see how it looked it once it passed through the Gateway and regional processor:


OK, we can see a few changes now.

  • The session and sequence numbers have been populated (1947392344);
  • The I/O identifier in block 2 has been updated to track that it is now an "Output" message;
  • The additional data within Block 2 is a combination of the input time, date, BIC, session and sequence numbers, output date/time, and priority;
  • The trailer has been updated with a message authentication code (MAC) calculated based on the entire contents of the message using a pre-shared key and a secret algorithm;
  • Additionally, a checksum of the message body has been stored within the trailer’s “CHK” tag. This is used by the network to ensure message integrity.

I also took a look at the entire outbound message history, just to see all the “Success” and “No violation” statements to make it feel even more awesome!

Phase 8 – Celebrate whilst reflecting on the meaning of life the universe and everything

So that's that really...

With a bit of research and support I was able to demonstrate a PoC for introducing a fraudulent payment message to move funds from one account to another, by manually forging a raw SWIFT MT103 single customer credit transfer message, and leveraging various system trust relationships to do a lot of the hard work for me!

As mentioned briefly in the introduction, this is not something I have really seen or heard of happening in practice or in the "wild". Perhaps because it clearly takes a lot of work... and there is a huge margin for error. However, if an adversary has spent enough time inside your network and has had access to the right documentation and resources, this may be a viable attack vector. It definitely has its benefits:

  • No need to compromise multiple payment operators;
  • No requirement to compromise - or establish a foothold within - the SWIFT Secure Zone;
  • No requirement to bypass MFA and gain credentials for a messaging interface;
  • No generation of application user activity logs;
  • No payment application login alerts;
  • No bespoke app-specific and tailored malware;
  • And all the other things associated with the complex task of gaining and leveraging payment operator access.

All an attacker may need to do is compromise one specific user on the corporate network: a Message Queue administrator.

The industry is spending a lot of time and effort focused on securing their payment systems, applications, processes, and users to keep - among other things - payment operators safe, Messaging Interfaces locked down, and SWIFT systems isolated. But the reality is,; the most valuable and most powerful individual in the entire model, might just be a single administrator!

As always, a security model is only as strong as its weakest link. If you're not applying the same level of security to your wider institution, there may very well be many weak links within the wider network which chain together and lead to the comrpomise of systems which feed into your various payment environment.

Perspective and Defense

I think the main thing to remember when reflecting on this research is that it did not abuse any vulnerabilities within the target institution's systems, or even vulnerabilities or weaknesses within the design of their architecture. It simply leverages the legitimate user access of the Message Queue administrators and the trust relationships that exist by design within these types of large-scale payment processing systems.

So the harsh reality is, there is no particular list of recommendations for preventing this type of attack in itself. However, the main point to drive home is that you must ensure the security of your users - and overall organisation - is of a high enough standard to protect your highest privileged users from being compromised. Things such as:

  • Strong monitoring and alerting controls for anomalous behaviour;
  • Requirements for Multi-Factor authentication for access to critical infrastructure;
  • Segregation of critical infrastructure from the wider general IT network;
  • Strong password policies;
  • Well rehearsed incident detection and incident response policies and procedures;
  • Frequent high-quality security awareness training of staff;
  • Secure Software Development training for your developers;
  • Routine technical security assessments of all critical systems and components;
  • The use of 3rd party software from reputable and trusted vendors;

However, in the context of Message Queues, there is one particular control which I think is extremely valuable: The implementation of channel specific message signing! This, as demonstrated by SWIFT's LAU control, is a good way in which to ensure the authenticity of a message.

As discussed, LAU is - as far as I know at the time of writing - a SWIFT product / message partner specific control. However it's concept is universal and could be implemented in many forms, two of which are:

  • Update your in-house application's to support message signing, natively;
  • Develop a middleware component which performs message signing on each system, locally.

This is a complex requirement as it requires considerable effort on the client’s behalf to implement either approach. However, SWIFT provides guidance within their Alliance Access Developers guide on how to implement LAU in Java, Objective C, Scala and Swift;

  1. Strip any S block from the FIN message input. Keep only blocks 1: through 5;
  2. Use the FIN message input as a binary value (unsigned char in C language, byte in Java). The FIN message input must be coded in the ASCII character set;
  3. Combine the left LAU key and the right LAU key as one string. The merged LAU key must be used as a binary value (unsigned char in C language, byte in Java). The merged LAU key must be coded in the ASCII character set;
  4. Call a HMAC256 routine to compute the hash value. The hash value must also be treated as a binary value (unsigned char in C language, byte in Java). The HMAC size is 32 bytes;
  5. Convert the HMAC binary values to uppercase hexadecimal printable characters.

An example of how this may work in the more flexible middleware solution proposed is where the original service is no longer exposed to the network, and is altered to only communicate directly with the custom "LAU-eqsue" service on its local host. This service would then sign and route the message to its respective queue.

When received, the core of the recipient payment service would seek to retrieve its messages from the queues via the "LAU-esque" signing middleware, which would retrieve the message and subsequently verify its origin and authenticity by re-calculating the signature using their shared (secret) keys. Key-pairs could further be unique per message flow. This design could allow for the signing to be used as a way to validate the origin of a message even if it had passed through multiple [local] intermediary systems.

As a final bit of creative effort, I made yet another diagram to represent what this could perhaps look like - if life was as easy as a diagram:

If you made it this far thanks for reading all... ~6k words!? I hope you found some of them interesting and maybe learned a thing or two!


I'd like express our gratitude to the institution who facilitated this research, as well as specifically to the various SMEs within that institution who gave their valuable time to support it throughout.