ISO 20022, Pain001 and payment of your salary
At Pentagrid, we occasionally review our clients' internal processes to identify IT security risks. When we discovered that large sums of money are transferred with just a few clicks and no transaction verification, we helped securing the process. At the same time, we developed a tool to support this improvement.
The following blog post outlines the complexity of pain.001 files, the risk of transfering payment files via insecure channels and the problem of reviewing payment details when the pain.001 file is uploaded into an e-banking interface. Pentagrid's tool follows a diff-like approach to detect changes among monthly payments. The "riskless pain" tool is available on Github.
Introduction to ISO 20022, pain.001 and salary payments
If you get paid a salary, it is likely that the payment will be transfered to the bank as part of a pain.001 file. In national and international banking, there is a common (and machine-readable) way of talking about such bank payments and other transactions. ISO 20022 defines this "standard for electronic data interchange". One part of it is Customer Credit Transfer Initiation (pain.001). As the name implies, it is used when a bank customer (e.g. a company) would like to instruct the bank to make payments. In essence, it is a standardised XML message saying something like Please use my account with IBAN CH9999999910378969399 to pay CHF 500.00 to Bob Foo, Zurich with IBAN LI7008805599999999999.
The pain.001 format supports thousand of payments in one single file. It also accommodates a wide range of special cases, including intermediary banks and cheques, which leads to complexity. This complexity is the first challenge: While verifying a pain.001 file against its XML schema is straightforward, ensuring that it matches the human intent behind the payment is not. The complexity increases when supporting all the various formats and versions. There are old formats/specifications and new ones (versions) as well as country-specific specifications. Especially the old specifications include questionable design decisions. For instance, the old Swiss format uses the <BIC> tag to represent a bank's clearing number, while newer international formats use <BICFI> for the actual Bank Identifier Code. Add to this the many optional fields, and it becomes easy to generate a technically valid but semantically dubious pain.001 file.
Let's look at an example of Please use my account with IBAN CH9999999910378969399 to pay CHF 500.00 to Bob Foo, Zurich with IBAN LI7008805599999999999. The follwing XML file is in the new Swiss format pain.001.001.09.ch.03:
<?xml version="1.0" encoding="UTF-8"?> <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.ch.03.xsd"> <CstmrCdtTrfInitn> <GrpHdr> <MsgId>MSG-2025-05-21T13:44:43</MsgId> <CreDtTm>2025-05-21T13:44:43</CreDtTm> <NbOfTxs>1</NbOfTxs> <CtrlSum>500.0</CtrlSum> <InitgPty> <Nm>Example AG</Nm> <CtctDtls> <Othr> <ChanlTp>NAME</ChanlTp> <Id>Infoniqa ONE Start </Id> </Othr> <Othr> <ChanlTp>VRSN</ChanlTp> <Id>V-2025.00)</Id> </Othr> <Othr> <ChanlTp>PRVD</ChanlTp> <Id>Infoniqa Schweiz AG</Id> </Othr> <Othr> <ChanlTp>SPSV</ChanlTp> <Id>2.0</Id> </Othr> </CtctDtls> </InitgPty> </GrpHdr> <PmtInf> <PmtInfId>PMTINF-2025-05-21T13:44:43-1</PmtInfId> <PmtMtd>TRF</PmtMtd> <BtchBookg>true</BtchBookg> <PmtTpInf> <CtgyPurp> <Cd>SALA</Cd> </CtgyPurp> </PmtTpInf> <ReqdExctnDt> <Dt>2025-05-26</Dt> </ReqdExctnDt> <Dbtr> <Nm>Example AG</Nm> </Dbtr> <DbtrAcct> <Id> <IBAN>CH9999999910378969399</IBAN> </Id> <Tp> <Prtry>CND</Prtry> </Tp> </DbtrAcct> <DbtrAgt> <FinInstnId> <ClrSysMmbId> <ClrSysId> <Cd>CHBCC</Cd> </ClrSysId> <MmbId>774</MmbId> </ClrSysMmbId> </FinInstnId> </DbtrAgt> <CdtTrfTxInf> <PmtId> <InstrId>287</InstrId> <EndToEndId>001283545D024C86401A11A21A2B12CF</EndToEndId> </PmtId> <Amt> <InstdAmt Ccy="CHF">500.00</InstdAmt> </Amt> <CdtrAgt> <FinInstnId> <BICFI>LILALI2XXXX</BICFI> </FinInstnId> </CdtrAgt> <Cdtr> <Nm>Bob Foo</Nm> <PstlAdr> <StrtNm>Foo 13</StrtNm> <PstCd>8400</PstCd> <TwnNm>Zurich</TwnNm> <Ctry>CH</Ctry> </PstlAdr> </Cdtr> <CdtrAcct> <Id> <IBAN>LI7008805599999999999</IBAN> </Id> </CdtrAcct> <RmtInf/> </CdtTrfTxInf> </PmtInf> </CstmrCdtTrfInitn> </Document>
Some of the complexity we encounter in these files may be intentional and necessary for certain use cases. However, from our perspective, it also introduces potential issues. For instance, while the pain.001 file explicitly specifies the recipient's bank using a BIC (e.g., LILALI2XXXX), the IBAN itself also encodes a bank identifier (in this case, the LI7008 prefix). So what happens if the bank indicated by the IBAN doesn't match the one specified in the BIC? Which one takes precedence? This scenario is likely addressed somewhere in the ISO 20022 specification. But it's easy to get wrong in practice. We suspect there are financial systems out there that mishandle such discrepancies.
Trust or how the file reaches the bank
Obviously, if you instruct your bank to do a payment with an attacker-controlled pain001 file, this is bad news and your money (which was supposed to be your employee's salaries) is gone.
You might say that an untrusted pain001 file should never reach the bank and you are absolutely right. In theory, you should trust whatever creates such files, such as your book keeping software (e.g. Infoniqa, SAGE, Bexio, Abacus just to name a few here in Switzerland). But how does the XML file from your software reach the bank?
One option (at least here in Switzerland) is to transfer the pain001 file directly from the book keeping software to the bank via EBICS. EBICS is again a different standard, an XML protocol used to login to the bank account and then send the pain001 file (yes, XML in XML). The protocol can be used to do a lot of other things, too. EBICS has it's own complexity we don't want to look at today (e.g. your book keeping software has access to your bank account) but at least nowadays if configured correctly this should use TLS and in-protocol mutual public key authentication (yes, not mutual TLS usually). We've tested many EBICS systems before and you can read about an XSS vulnerability here, but for the scope of this article, EBICS is good from a trust perspective. The pain001 reaches the bank directly over a secure channel.
Another option is to generate the file in the software directly and then the same person uploads the file to the bank. This is usually fine, too.
However, in practice pain001 files are also exported from book keeping software and kept as files. Many companies use external book keeping services or payroll contractors, which then transfer the pain001 file to the customer to do the actual payment in the electronic banking portal. Unfortunately, often enough the files are sent via email or other untrusted channels. As standard email is unauthenticated, this means the recipient needs to verify the contents of the pain001 file before doing the payment. And this is where it gets tricky. We've seen multiple Chief Financial Officers (CFO) of companies receiving pain001 files for salary payment and sending millions of swiss francs without verifing the contents of the pain001 file, doing this every single month of the year. Imagine one spoofed email that sends a pain001 file one day early before the regular salary payments are due... and there could be immediate financial damage.
The most important and obvious thing to do is to secure the channel first and use something secure rather than email. This is what we recommend to do first. But then you might still wonder what kind of payments are included in the pain001 file you get: Control is better than trust. And honestly, investing a minute to check the file before you send a couple of million swiss francs looks like a good trade-off.
This leads to the next problem in the chain: E-Banking interfaces of banks. It is very unfortunate that many banks do not handle pain001 files in a user friendly way (although we heard at least from one bank doing it right in Switzerland). When uploading the file, most banks check the integrity of the file, but then only show the total amount and the number of transactions, but no list of transactions. This means it is still not possible to know which recipient or which bank will get the money. And in many cases the "independent payment verification" on the mobile phone will only show the same data instead of the list of transactions. Even the four-eyes principle with joint signatories doesn't help in this case if all four eyes don't see any transaction details.
As usual there are multiple things that usually have to be wrong, but we saw the problem in practice.
Riskless pain tool and salary use case
For the simple case of reoccuring monthly salary payments where a lot of the transactions stay the same month after month, we created a tool to look at a pain001 file or compare two pain001 files. Our goal was to show where money is going to end up, therefore we parse all CdtTrfTxInf tags from a pain001 file, but don't care about the rest. E.g. we don't care about from which account the money is paid from. With the creation of our tool the complexity of pain001 started to show. Due to all the optional fields that are possible which might reroute a payment to a malicious actor, it is hard for a GUI tool to show all the data that could be represented. We went for a pragmatic approach which shows the most common fields. But to be on the secure side we can also display the XML tree (CdtTrfTxInf). The tool will also try to match similar payments of two pain001 files are compared, so modifications can be detected and a diff-style difference between the payment XML tree can be displayed in the GUI.
We hope the "riskless pain" tool is helpful to all people who would like to show details of pain.001.001 files. However, there is absolutely no warranty whatsoever, because simply put, pain001 files are pretty complex. We assume that when looking at all CdtTrfTxInf XML tag you should know to whom you send money, but this assumption could be wrong. However, we support version pain.001.001.01 all the way up to pain.001.001.11.xsd and also the Swiss standard. We're pretty sure the tool will fail for other national standards that are not compatible with the ISO20022 international XML schemes. We also have only tested the tool with a couple of XML test files that are again only a selection from a handful of XML schemas out of the many supported ones. We did not look through each and every version to make sure that looking at CdtTrfTxInf tags in the XML files is sufficient. We haven't even read the ISO2022 standard. So please take this with a grain of salt and let us know if you find a way to transfer money to an attacker without the GUI warning of a modification (or showing any details in the XML tab).
If you are an expert for ISO 20022 (e.g. a developer of a library for pain001), we would like to hear your feedback.
We would also like to see more banks (especially in Switzerland) who show transaction details for pain.001.001 files or even a diff-style analysis tool in their electronic banking interfaces. It would definitely help to cover one of the very common use cases and could actually prevent fraud. We would be happy if our "riskless pain" tool gets obsolete in the future.