escape csv formulae

This is only needed for unsecure spreadsheet applications (hi Google Docs and MS Excel) that load formulae by default.

See https://owasp.org/www-community/attacks/CSV_Injection for some mitigation explanation. This is not complete, but it should be OK for now.
This commit is contained in:
Glandos 2022-05-14 15:53:15 +02:00
parent 8b9370088f
commit 042b33aeb2
2 changed files with 47 additions and 1 deletions

View file

@ -596,6 +596,38 @@ class ExportTestCase(IhatemoneyTestCase):
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
def test_export_escape_formulae(self):
self.post_project("raclette", default_currency="EUR")
# add participants
self.client.post("/raclette/members/add", data={"name": "zorglub"})
# create bills
self.client.post(
"/raclette/add",
data={
"date": "2016-12-31",
"what": "=COS(36)",
"payer": 1,
"payed_for": [1],
"amount": "10.0",
"original_currency": "EUR",
},
)
# generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv")
expected = [
"date,what,amount,currency,payer_name,payer_weight,owers",
"2016-12-31,'=COS(36),10.0,EUR,zorglub,1.0,zorglub",
]
received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected):
self.assertEqual(
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
class ImportTestCaseJSON(CommonTestCase.Import):
def generate_form_data(self, data):

View file

@ -152,6 +152,17 @@ def list_of_dicts2json(dict_to_convert):
return BytesIO(dumps(dict_to_convert).encode("utf-8"))
def escape_csv_formulae(value):
# See https://owasp.org/www-community/attacks/CSV_Injection
if (
value
and isinstance(value, str)
and value[0] in ["=", "+", "-", "@", "\t", "\n"]
):
return f"'{value}"
return value
def list_of_dicts2csv(dict_to_convert):
"""Take a list of dictionnaries and turns it into
a csv in-memory file, assume all dict have the same keys
@ -164,7 +175,10 @@ def list_of_dicts2csv(dict_to_convert):
# (expecting a sequence getting a view)
csv_data = [list(dict_to_convert[0].keys())]
for dic in dict_to_convert:
csv_data.append([dic[h] for h in dict_to_convert[0].keys()])
csv_data.append(
[escape_csv_formulae(dic[h]) for h in dict_to_convert[0].keys()]
)
# csv_data.append([dic[h] for h in dict_to_convert[0].keys()])
except (KeyError, IndexError):
csv_data = []
writer = csv.writer(csv_file)