steve icon indicating copy to clipboard operation
steve copied to clipboard

Repeated StartTransaction Messages Accepted Post StopTransaction Without Database Records

Open Tano-Coppoletta opened this issue 9 months ago • 4 comments

Checklist

  • [ ] I checked other issues already, but found no answer/solution
  • [x] I checked the documentation and wiki, but found no answer/solution
  • [ ] I am running the latest version and the issue still occurs
  • [x] I am sure that this issue is about SteVe (and not about the charging station software or something unrelated to SteVe)

Specifications

SteVe Version     : 3.6.0
Operating system  : Ubuntu 22.04.1
JDK               : openjdk version "11.0.20.1"
Database          : version 1.0.3

Expected Behavior

Once a StartTransaction message is sent and subsequently stopped with a StopTransaction message, any further attempts to send the same StartTransaction message should not be accepted, as the transaction has already concluded. The system should respond with an appropriate error message indicating that the transaction is already stopped. Additionally, attempts to start a new transaction should require a unique transaction initiation message, rather than accepting a message that differs only by a single parameter such as meterValue.

Actual Behavior

After stopping a transaction, sending the same StartTransaction message again is erroneously accepted by the system, but no new record is created in the database. This could mislead users into believing that a new transaction has started when, in fact, it has not. Furthermore, it's possible to initiate a new transaction by altering a single field in the StartTransaction message, which could lead to transaction duplication and data inconsistencies.

Steps to Reproduce the Problem

  1. Send a StartTransaction message and receive a transactionId.
  2. Send a corresponding StopTransaction message to end the transaction.
  3. Re-send the same StartTransaction message. Notice that the system accepts the message, but no record is created in the database, the previous transactionId is returned by the server.
  4. Modify a single parameter (e.g., meterStart) in the StartTransaction message and send it again. Observe that a new transactionId is generated and a new transaction appears to start.

Additional context

This behavior can lead to significant issues with transaction management, where the system's state does not accurately reflect the actual transactions that have occurred. It can cause confusion, make it difficult to track transaction history accurately, and might have implications for billing and auditing processes.

Thank you for your time and consideration in addressing this issue.

Tano-Coppoletta avatar Nov 07 '23 22:11 Tano-Coppoletta

Steps to Reproduce the Problem

  1. Send a StartTransaction message and receive a transactionId.
  2. Send a corresponding StopTransaction message to end the transaction.
  3. Re-send the same StartTransaction message. Notice that the system accepts the message, but no record is created in the database, the previous transactionId is returned by the server.
  4. Modify a single parameter (e.g., meterStart) in the StartTransaction message and send it again. Observe that a new transactionId is generated and a new transaction appears to start.

i don't know about you, but the logic behind these steps is sound to me. here is the justification.

if the exact same message (semantically) is sent twice, we interpret this as a retry (due to technical reasons). even if one field is different (different semantics), a different and new transaction.

goekay avatar Nov 07 '23 23:11 goekay

Thank you for your response and the explanation of the message handling logic.

I would like to point out a potential security issue that arises from the ability to resend an identical StartTransaction message. Consider a scenario where a user legitimately starts a transaction to charge their car and captures the OCPP StartTransaction message. If the system treats the resending of this exact message as a legitimate retry and starts a new charging session without recording it in the database, the user could potentially reuse the same message to repeatedly initiate charging sessions in the future without authorization and without incurring any cost.

Given that charging stations are physically accessible, an attacker—or in this case, even a regular user—could exploit this to charge their vehicle multiple times while avoiding payment, as the transactions are not being recorded after the first instance. This could lead to unauthorized use of electricity and financial losses.

Tano-Coppoletta avatar Nov 08 '23 16:11 Tano-Coppoletta

i could undo the changes and treat every message as a brand new StartTransaction, but then i would clutter the database with noise and effectively bring back the pains expressed in #81. the code was added after the report in the issue. i am not sure how big of an issue this is with our user base currently after a couple of years. this would also increase the ghost transactions which need to be handled after the fact.

considering rejections of repeated StartTransactions: we cannot reject an incoming StartTransaction. this is according to ocpp. a proper StartTransaction response needs to be sent back. and there is no possibility to express a failure within the StartTransaction response payload. we can return a general CallError, but this will result in station retrying for X times in intervals of Y. it will drop the message after exhausting the retries. so, this option would result in information loss and we cannot employ this approach. unfortunately, as far as station is concerned, the first StartTransaction was communicated, but the reply of steve did not arrive at station properly.

we could detect the subsequent retries and return with an AuthorizationStatus that is not Accepted in order to signal to station to stop this immediately and by doing that close the attack vector you mentioned. but then, if this is a valid retry we would reduce the usability, operation and customer experience.

another possibility is to "seal" a transaction after a while the first StopTransaction from station arrives, and not allow any more StartTransaction retries by returning CallError.

however, all these possibilities assume steve to be a tight controller and gatekeeper for the sake of security. there is a tone in spec that offline behaviour of stations is as important as online behaviour. therefore, all these StartTransactions and StopTransactions can/should be treated as "happenings/events of the past being reported to backend". this diminishes the role of the backend, and we cannot be as tight as you would like. the station must do the right thing.

goekay avatar Nov 08 '23 23:11 goekay

Hello, I think that this behavior could lead to a replay attack. Suppose I am an attacker and I store the StartTransaction message that I have sent to Steve. Then I charge my car and after that the transaction is stopped using a StopTransaction message, the database records this transaction and everything works perfectly. Next time that I want to charge the car, I can reuse the StartTransaction message used before (even if that transaction is already finished due to the StopTransaction message) and no records will be produced on the database but the transaction is actually allowed so the charge point is authorized to start charging my car. In this second case I won't be charged for charging my car because there is no such transaction in the database.

I also want to let you know that we are planning to submit this as cve and publishing this result in an academic paper.

Thank you for your attention to this matter.

Tano-Coppoletta avatar Dec 02 '23 22:12 Tano-Coppoletta