Strings oder C-Felder?

Alles rund um die Sprache ABAP®: Funktionsbausteine, Listen, ALV
15 Beiträge • Seite 1 von 1
15 Beiträge Seite 1 von 1

Strings oder C-Felder?

Beitrag von DRABAP (ForumUser / 30 / 0 / 1 ) »
Oftmals steht ein Programmierer vor der Frage, ob er seine Daten lieber in C-Feldern oder Strings speichern soll.
Im folgenden werden ein paar Anhaltspunkte für die Entschiedungsfindung aufgelistet.
1. Unterschiede im Speicherlayout
C-Felder werden en bloc direkt im Speicher abgelegt. Mehrere C-Felder innerhalb einer Struktur liegen also mehr oder weniger (Alignmentlücken!) hintereinander m Speicher.
String besitzten wie Tabelle eine Indirektion. Im Header werden nur Verwaltungsinformatinoen gespeichert. Strings haben somit einen gewisseen Overhead (von ca. 16-20 bytes). Der Overhead erhöht sich nocheinmal, da immer etwas mehr Speicher als notwendig angefordert wird (als Reserve für Verlängerung des Strings). Ein C-Feld verbraucht immer den Speicher, der bei der Deklaration angefordert wurde. Strings können quais beliebig lang werden und passen ihre Länge zur Laufzeit an.
2. Unterschiede bei schliessenden LeerzeichenString besitzen im Gegensatz zu C-Feldern schliesßende Leerzeichen. Dies kann oft zu Verwirrungen beim Vergleich mit C-Feldern führen, da diese per Defintion keine schließenden Leerzeichen besitzen können.
3. Unterschiede innerhalb von Strukturen
Strukturen in denen Strings enthlten sind unterliegen stärken Restriktionen als Strukturen mit C-Feldern. Dies merkt man vor allem bei der Kompatibilitätsprüfung.
4. Unterschiede in der PerfromanceDa Strings einen Overhead besitzen sind sie langsamer, wenn die Länge sehr klein ist (10 zeichen z.B.). Bei längeren Strings ist ihre Performance jedoch oftmals besser als bei C-Feldern, da ein String die Länge miführt und diese nicht bei jeder Operation - wie beispielsweise einem Concatenate - immer wieder aus neue bestimmen muss.
5. Unterschiede beim Kopieren
C-Felder werden bei jeder Zuweisung kopiert. Für String gibt es ein sog. copy on write. Hierbei teilen sich mehrere String die Daten. Erst weddn einer der String seinen Wert verändern möchte, erfolgt ein Kopieren. Dieses Verhalten ist nach außen nicht sichtbar. Die Perfromance aber z.B. bei der Parameterübergabe kann bei großen String, die selten geändert werden (weil sie z.B. nur eine weitere Funktion weitergereicht werden) deutlich besser sein als bei C-Feldern.
6. Unterschiede auf der Datenbank
Bei String auf der Datenbank unterscheidet man in lange und kurze Strings. Lange werden in LOBS abgelegt und können nicht Teil der Where-Bedingung beim Select sein. kurze Strings werden als VARCHAR abgelegt werden und können in der WHERE-Bedingung wie C-Felder behandelt werden. Beim Lesen von der Datenbank wird versucht, die String zu sharen. D.h. wenn beispielsweise sehr oft ein Name - wie beispielsweise "Meier" oder "München" - gelesen wird, so teilen sich die entsprechden String den Speicher. Es ist jedoch nicht garantiert, dass String mit dem gleichen Wert sich den Speicher stets teilen.

Strings bieten vor allem den Vorteil, dass man ihre Länge nicht statisch spezifizieren muss. Bei C-Feldern muss man immer den maximalen berücksichtigen. In der Regel bleibt jedoch der meiste Teil des Speichers ungenutzt. Trotzdem sollte man nicht bedenkenlos jedes kleine C-Feld durch einen String ersetzen, da Strings einen gewissen (konstanten) Overhead bei Perfromance und Speicher besitzen.
Ab 6.10 können auch Stringliterale verwendet werden. Sie werden durch den Backquote eingeschlossen (`Test`). Neben Strings gibt es auch XSTRING für Folgenvon Bytes. Strings können ab 4.6D(?) auch auf Dynpros eingesetzt werden. Beim RFC wahrscheinlich auch ab 4.6D. Man in Strings keine Feldsymbole hineinsetzten. Stattdessen sollte man Offset-Längen-Zugriffe verweden.

Diese List zu String ist sicherlich nicht vollständig. Vielleicht kann man sie im Laufe der zeit weiter vervollständigen.
Dr. ABAP

gesponsert
Stellenangebote auf ABAPforum.com schalten
kostenfrei für Ausbildungsberufe und Werksstudenten


Beitrag von DRABAP (ForumUser / 30 / 0 / 1 ) »
.
Zuletzt geändert von DRABAP am 07.01.2003 19:55, insgesamt 1-mal geändert.
Dr. ABAP

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
Da muß ich doch glatt mal prüfen, ob es mir gelingt, auch für Strings einen Funktionsbaustein zu schreiben, der den importierten Parameter initialisiert.

Bisher habe ich es immer nur mit Feldern vom Typ C versucht.
Bei CHAR-Feldern klappt das, z.B.

Code: Alles auswählen.

REPORT z.
DATA: d(4) VALUE 'test'.
CONSTANTS: c(4) TYPE c VALUE 'abcd'.
SET BLANK LINES ON.
WRITTE: / d COLOR COL_POSITIVE, c COLOR COL_TOTAL, '1234' COLOR COL_NEGATIVE.
CALL FUNCTION 'Z_TEST' EXPORTING p = : c, d, '1234'.
WRITTE: / d COLOR COL_POSITIVE, c COLOR COL_TOTAL, '1234' COLOR COL_NEGATIVE.
(mußte leider noch mal editieren, das Beispiel war ja nicht mal syntaktisch korrekt.)

Nach den 3 FB-Aufrufen ist nicht nur D initial, sondern auch C und sogar das Literal '1234'!!!!

Ist natürlich ein (leicht reproduzierbarer) Fehler im SAP-Kernel (seit mind. 4.0), aber SAP behauptet, das wäre kein wirklicher Fehler, und ich würde falsch programieren.
Bestimmte Felder lassen sich so aber nicht (mehr) manipulieren, z.B. SY-REPID.

Hat jemand von Euch 'ne Idee, wie ich das gemacht habe?

Noch 2 Tips.
Wenn man einen Watchpoint auf den Parameter setzt, zieht dieser Watchpoint nicht, obwohl der Wert anschließend geändert ist.
Wenn man im Einzelschritt-Debugging (immer mit F5) durchgeht, wird das Feld NICHT verändert.

Oder hätte das Raten mehr Spaß gemacht, wenn ich diese Info (und den Hinweis auf CONSTANTS und Literale) weggelassen und Euch gefragt hätte, wie Ihr versuchen würdet, das Problem zu lösen.

Weil die Fehlersuche in einem sehr umfangreichen FB so nicht gerade Spaß gemacht hat, war ich ziemlich verärgert, weil SAP den Fehler nicht abstellen wollte.
Nachdem das Flag immer zurückgesetzt wurde und ich die Stelle nicht gefunden habe, habe ich es halt die DATA-Anweisung durch CONSTANTS ersetzt. SAP (der first level support) riet mir, zur Fehlerbehebung doch eine Anweisung

Code: Alles auswählen.

con_x = 'X'.
einzufügen.
Zuletzt geändert von Frank Dittrich am 09.01.2003 13:15, insgesamt 2-mal geändert.

Beitrag von DRABAP (ForumUser / 30 / 0 / 1 ) »
Schwierige Frage! Ich tippe mal, Du hast einen ASSIGN <..>+x TO <...> oder einen DO VARYING verwendet. Ich schätze aber mal, dass Dein Funktionbaustein bei Strings "versagt" ;-).
Dr. ABAP

Beitrag von black_adept (Top Expert / 4159 / 136 / 960 ) »
Hi Frank,

ich hab sowas ähnliches vor ein paar Jahren auch mal gemacht, aber ich habe leider vergessen wie man genau vorgehen muss.

Die Doku zu dem verwendeten Befehl (bzw. das wohl aus Versehen gepostete Coding in einer 4.0 F1-Hilfe) war eh nur für den "internen Gebrauch" wenn ich mich recht entsinne.

Knackpunkt war die Verwendung der scheinbar undokumentierten

Code: Alles auswählen.

DATA P POINTER.
Anweisung.
Leider kriege ich nicht mehr zusammen wie man da Vorgehen musst um P "interessante" Werte zu überreichen - aber man konnte damit sowohl lesend als auch schreibend zugreifen und dann Variablen im Hauptspeicher "verschieben". Und mit so einem Trick bekommst du auch Konstanten geplättet.

Ich denke mal, dass SAP die Doku rausgenommen hat ob der Gefährdung die von so einem Unsinn ausgeht - aber das klappt bestimmt immer noch. In dem aktuellen 4.6c System wo ich grade bin ergibt sich zumindest kein Syntaxfehler bei obiger Anweisung.
live long and prosper
Stefan Schmöcker

email: stefan@schmoecker.de

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
DRABAP hat geschrieben:Schwierige Frage! Ich tippe mal, Du hast einen ASSIGN <..>+x TO <...> oder einen DO VARYING verwendet. Ich schätze aber mal, dass Dein Funktionbaustein bei Strings "versagt" ;-).
Nicht schlecht.
Hat auch mal funktioniert für Parameter mit Bezug auf in einer Typgruppe definierte komplexe Typen . Nach ASSIGN konnte man Komponenten des Feld-Symbols verändern.
Ist inzwischen behoben. (Und war, wenn ich mich recht erinnere, einer der wenigen Fälle, wo sich jemand für den gemeldeten Fehler bedankt hat.)

Natürlich hätte das nicht bei Literalen und vermutlich auch nicht bei Konstanten funktioniert.
(Und der Watchpoint im Debugger hätte bestimmt geholfen, die Stelle zu finden.)

Zu den Strings bin ich noch nicht gekommen.
Wenn es funktioniert, dürften die Folgen allerdings u.U. katastrophal sein (wg. Copy on Write - wer weiß, ob das Write erkannt wird).

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
black_adept hat geschrieben:Knackpunkt war die Verwendung der scheinbar undokumentierten

Code: Alles auswählen.

DATA P POINTER.
Anweisung.
Also ich habe in dem Fall keine nur zur internen Verwendung vorgesehenen Befehle genutzt.
Ich denke mal, dass SAP die Doku rausgenommen hat ob der Gefährdung die von so einem Unsinn ausgeht - aber das klappt bestimmt immer noch. In dem aktuellen 4.6c System wo ich grade bin ergibt sich zumindest kein Syntaxfehler bei obiger Anweisung.
Dazu kann ich nur sagen, daß Security By Obscurity m.E. wenig taugt.
Ein potentieller Angreifer weiß dann u.U. mehr als der Anwender.

Besser wäre, SAP weist auf das Problem hin und liefert Analyse-Programme, um solche Fälle zu finden.

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
DRABAP hat geschrieben:Schwierige Frage! Ich tippe mal, Du hast einen ASSIGN <..>+x TO <...> oder einen DO VARYING verwendet. Ich schätze aber mal, dass Dein Funktionbaustein bei Strings "versagt" ;-).
Mir fällt gerade ein, es gibt noch 'ne andere Möglichkeit, Deine Idee zu verstehen:
also im FB SY-CPROG oder den ABAP Callstack auswerten, per LOAD bzw. SCAN ABAP-SOURCE die globalen Variablen ermitteln, mit der nur für SAP-interne Verwendung vorgesehenen Variante der ASSIGN-Anweisung die Variable des aufrufenden Programmes ansprechen, ...
Habe ich noch nicht getestet. Würde aber vermutlich nur für globale Variablen des aufrufenden Programms funktionieren. Vielleicht verweigert das System aber auch zurecht die Änderung der Variablen, die als IMPORTING-Parameter an den FB übergeben wurde.

Ist viel zu kompliziert.
Weitere Hinweise:
Der FB hat eine globale Schnittstelle. (Parameterübergabe per Referenz ist wohl klar, ich will ja nicht eine Kopie ändern.)
Eventuell würde auch ein FB mit lokaler Schnittstelle funktionieren, der den Parameter an einen weiteren FB mit globaler Schnittstelle durchreicht.
Daß die übergebenen Parameter alle die gleiche Länge haben, ist Absicht.
Um C-Felder verschiedener Länge zu initialisieren, ist einiger Mehraufwand nötig, damit nicht nach dem ersten Aufruf des FB für ein Feld mit 8 Zeichen Länge der 2. Aufruf mit einem Feld von 4 Zeichen Länge mehr als die 4 Zeichen überschreibt.
(Iin meinem Beispiel habe ich noch einen Syntaxfehler beseitigt und nach EXPORTING p = eingefügt.)

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
Vieleicht hätte ich mit dem Beispiel doch in das Forum "nicht zuordenbar" (schreckliches Wort, finde ich) umziehen oder zumindest ein neues Thema anfangen sollen.
(Der ursprüngliche Beitrag wäre m.E. geeignet, entweder in diesem Forum, im Forum "Getting started" oder in einem noch zu schaffenden Forum "Tipps & Tricks" oder so zur Verfügung zu stehen. Der Rest des Threads paßt irgendwie nicht so richtig dazu.

Die Erweiterung auf beliebig lange C-Felder war nicht schwer:

Code: Alles auswählen.

FUNCTION z_test.
*"----------------------------------------------------------------------
*"*"Globale Schnittstelle:
*"  IMPORTING
*"     REFERENCE(P)
*"----------------------------------------------------------------------
  DATA: t, l TYPE i, c.
  DATA: i TYPE i.
  DESCRIBE FIELD p LENGTH l IN CHARACTER MODE TYPE t COMPONENTS c .

*  MESSAGE s000(38) WITH t l c.
  CHECK t EQ 'C'.
  WHILE i LT l.
    CALL FUNCTION 'Z_TEST2'
      EXPORTING
        p2 = p+i(1).
    ADD 1 TO i.
  ENDWHILE.
ENDFUNCTION.
Allenfalls zur Performance-Optimierung wäre noch mehr Aufwand nötig.

Mit einem halbwegs aktuellen 4.6D-Kernel-Patch unter 4.6B konnte ich den Fehler noch reproduzieren.
Mit dem WAS TestDrive for Linux 6.10 (mit dem Kernel-Patch-Level 185) und SAP DB klappt das Initialisieren nicht mehr so wie zu 4.6D noch.
Stattdessen kommt es zu einer A-Message (Message ID 00, Message Nummer 398), Text trotz SY-LANGU = 'D': Read-Only field P2 in ...
(den Rest des Textes lasse ich noch mal weg)

Wenn ich mit Einzelschritt-Debugging durch den FB gehen will, kommt es zusätzlich noch zum Durchstarten des Workprozesses (Lösche Modus 001 nach Fehler 023).

Was aber stattdessen immer noch klappt (habe ich vorher nur nicht getestet), ist die Umwandlung in Großbuchstaben:
Mein Report

Code: Alles auswählen.

REPORT  ztest                                   .
DATA: d VALUE 'd'.
CONSTANTS c VALUE 'c'.
SET BLANK LINES ON.
WRITE: / d, c, 'l', 'l2', 'tst', 'x'.
CALL FUNCTION 'Z_TEST' EXPORTING p =: d, c, 'l', 'l2', 'tst', 'x'.
WRITE: / d, c, 'l', 'l2', 'tst', 'x'.
BREAK-POINT.
ULINE.
produziert also die Liste:
  • d c l l2 tst x
    D C L L2 TST X
Um bei meinem Test-FB für weitere Aufklärung oder Verwirrung zu sorgen:
Jetzt das Phänomen:
Nachdem mein Programm einmal durchgelaufen ist, werden bei jedem weiteren Durchlauf in den WRITE-Anweisungen vor CALL FUNCTION nur noch die mit DATA definierten Felder korrekt ausgegeben. Die CONSTANTS und Literale sind schon in Großbuchstaben umgewandelt.

Abhilfe schafft hier nur das Neugenerieren des Programms ZTEST oder z.B. ein /$SYNC im OK-Code.
Die Änderungen schlagen also voll auf die gepufferte Programm-Load durch.

Durch weiteres Experimentieren habe ich eine Möglichkeit gefunden, den Fehler auszunutzen, um den Inhalt der C-Felder beliebig ändern zu können.
Auch hier haben die CONSTANTS und Literale zu Beginn des nächsten Durchlaufs die Werte, die ihnen beim vorhergehenden Programmstart zugewiesen wurden.

Die genannten beliebigen Änderungen können aber (im Gegensatz zu dem Initialisieren) nicht mehr versehentlich passieren, sondern erfordern schon böse Absicht des Programmierers.

Nachtrag zu STRINGS:

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
Wenn ich Variablen oder Konstanten des Typs STRING an Z_TEST übergebe (und intern eine Function Z_TEST3 aufrufe, klappt das Konvertieren in Großbuchstaben.
(Für DATA und CONSTANTS). Hier allerdings ohne Auswirkung auf den nächstfolgenden Programmstart. D.h., Änderungen wirken sich nicht auf die gepufferte Programm-Load aus.

Der Versuch, die Strings mit beliebigen Werten zu füllen, scheiterte dagegen erst mal mit Laufzeitfehler STRING_BAD_REF.

(Wenn ich als Entwickler das Programm auch noch selbst starten darf, und IF SY-UNAME EQ ...
erlaubt ist, wenn mir außerdem beim Programmstart niemand zusieht, kann ich aber auch noch die String-Konstanten beliebig manipulieren.
Das ist aber sowieso ein anderes Thema und würde eher in ein eigenes Forum "Sicherheit" gehören, wenn es das gäbe.)

Beitrag von Wolfgang G. Propfe (ForumUser / 23 / 0 / 0 ) »
Moin Frank:
Nachdem mein Programm einmal durchgelaufen ist, werden bei jedem weiteren Durchlauf in den WRITE-Anweisungen vor CALL FUNCTION nur noch die mit DATA definierten Felder korrekt ausgegeben. Die CONSTANTS und Literale sind schon in Großbuchstaben umgewandelt.


I am not surprised. Since by definition constants and literals cannot be changed (only by you :-) ), there is no need to copy them to a separate memory block. They can be embedded in the (compiled) source of the program. If you look at a .exe written in C, you also find all the literals embedded towards the end of the executable.

I am, however, surprised that you shared this. I thought you had enough troubleshooting to do, cleaning up after the 'low cost' alternatives... Have you seen the latest questions on sapfans? It gets worse every day.

Oh, and Happy New Year to you!

Regards,
Wolfgang

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
Hallo Wolfgang, habe leider etwas länger für die Antwort gebraucht.
Wolfgang G. Propfe hat geschrieben:I am, however, surprised that you shared this.
Na ja, noch habe ich hier ja nicht so viel verraten, außer DASS es irgendwie geht.
Und auf dumme Ideen kommen die Leute auch so genug, das beweisen ja auch diverse Fragen in sapfans-Foren.
Bis dann mal wieder jemand "HELP, SAPMSYST DELETED!!!" postet, weil er/sie mal probieren wollte, wie es geht:
ein im Internet gefundenes Programm zum "Verstecken" von ABAP source code bzw. Lesen des Quelltextes von "geschützen" Programmen auf SAPMSYST loszulassen, ohne den Quelltext vorher mal anzusehen geschweige denn zu verstehen, ob das Programm überhaupt für den bei SAPMSYST genutzten "hiding mechanism" paßt oder nicht - und dann jammern, weil es schiefgeht.
Solange solche Leute an produktive SAP-Systeme gelassen werden, braucht man sich doch wegen veränderter Konstanten noch die geringsten Sorgen machen.
(Kennen die einen Unterschied im Umgang mit einem produktiven SAP-System und ihrem privaten Win9x-System zu Hause?)

Ich gebe aber zu, daß ich in diesem Falle nicht so sehr an mögliche Sicherheitsrisiken gedacht habe. Im Gegensatz dazu ist Dir offenbar sofort bewußt gewesen, was ein solcher Bug bedeutet.

Andererseits: Wem nützt es, sich bzgl. Sicherheit etwas vorzumachen?

Wie gesagt, das eine Problem, daß versehentlich Konstanten initialisiert werden, die Fehlersuche erschwert wird ..., ist ja anscheinend zu 6.10 behoben.
Zu 4.6D aber noch nicht. Und eine minimale Änderung im Herangehen führt auch zu 6.10 noch zu einem Fehler, der sich entsprechend ausnutzen läßt.
Dabei hätte SAP seit Anfang November 1998 Zeit gehabt, das Problem zu lösen.
So lange ist es nämlich schon her, daß ich den Fehler zu 4.0B gemeldet habe. (Ich hab mal ein wenig herumgewühlt.)

Irgendwann Anfang November 1998 die Fehlermeldung, am 5. November noch ein kurzer Kommentar von mir, am 16. November 1998 Rückfrage vom Development Support mit der Bitte um Remote-Verbindung, um den Fehler reproduzieren zu können.
Daraufhin von mir am 16. November die Antwort mit einem kurzen Beispielprogramm. Ebenfalls noch am 16. November von mir die Info zu genau dem Fehler nachgeliefert, den ich auch jetzt zu Release 6.10 noch ausnutzen kann, um Konstanten (nicht nur vom Typ C) und Literale beliebig zu ändern.
Am 17. November 1998 habe ich ein Beispiel nachgeliefert, wie ich den Inhalt verschiedener Systemfelder initialisieren kann (wobei man wohl die meisten auch einfach hätte mit CLEAR initialisieren können).
Am 17. November kam auch gleich die Antwort (über die Reaktionszeiten konnte man also in diesem Fall nicht meckern).
Das potentielle Sicherheitsrisiko wurde NICHT als solches erkannt. Eher wurde der Fall wohl unter "unfähiger Programmierer" verbucht. Entsprechend wurde mir vom Development Support (und nicht, wie ich mich zu erinnern glaubte, vom First Level Support) empfohlen, den Fall doch zu umgehen oder die Konstante wieder mit ihrem ursprünglichen Wert zu belegen.
SAP Development Support hat geschrieben:Als Lösung sollten Sie also entweder ... ausführen oder aber die Daten ... explizit setzen.... CON_CHANGED = 'X'.
Nach meiner Beschwerde wegen des Syntaxfehlers hat der Entwickler mich zurückgerufen. (An das Gespräch kann ich mich nicht mehr erinnern, aber ich habe ihn wohl nicht überzeugen können, das Problem zu beheben.)
Und noch am 17.November 1998 wurde das Problem auf erledigt gesetzt, mit dem Versprechen
SAP Development Support hat geschrieben:Ich werde mit dem zuständigen Entwickler über eine Klarstellung in der Dokumentation sprechen.
- Vermutlich, damit nicht noch mehr Entwickler sich ähnlich blöd anstellen wie ich. Die "Klarstellung in der Doku" vermisse ich heute noch.

Nachdem ich Anfang 1999 auf meine Fehlermeldung zu IMPORTING-Parametern von FBs und Field-Symbols:

Code: Alles auswählen.

* 1. TOP-Include der Fugr.
...
TYPE-POOLS: YTEST.
FIELD-SYMBOLS: <I> TYPE YTEST_IMPORTING.
...

* 2. Funktions-Quelltext
FUNCTION_Y_TEST_1.
*"----------------------------------------------------------------------
*"*"Lokale Schnittstelle:
*"       IMPORTING
*"             REFERENCE(P_I) TYPE  YTEST_IMPORTING_A
*"----------------------------------------------------------------------
  ASSIGN P_I to <I>.
  PERFORM TEST_FORM.
  ...
ENDFUNCTION.
FORM TESTFORM.
  CASE <I>-DATUM.
    WHEN SY-DATUM.
      CLEAR <I>-DATLO.       " Dump notwendig!!!
    WHEN OTHERS.
      <I>-DATUM = SY-DATLO.  " Dump notwendig!!!
  ENDCASE.
ENDFORM.
die Antwort bekam, daß das Problem zu 4.5A gelöst ist (das "Danke" hatte ich irgendwie falsch in Erinnerung, vermutlich kannte SAP diesen Fehler schon, und ich habe wohl das Ironie-Tag in der Antwort vom 16.02.99: "Hier haben Sie eine echte Lücke gefunden." übersehen.) habe ich noch mal einen Anlauf unternommen:
Jedoch kann so ein Fehler absichtlich dazu ausgenutzt werden oder unbeabsichtigt dazu führen, daß innerhalb einer Funktion IMPORTING-Parameter überschrieben und verändert an den Aufrufer zurückgereicht werden!
Da in so einem Fall jedoch die Werte im aufrufenden Programm zu Folgefehlern führen können, sollte m.E. eine Lösung gefunden werden, die den Fehler unterbindet (notfalls Laufzeitfehler).
Wieder mit Beispielcode.

Am 17.02.1999 wieder die Antwort vom Development Support
SAP Development Support hat geschrieben:Dadurch kann es in Einzelfällen zu den von Ihnen entdeckten Inkonsistenzen kommen. Eine solche Fehlersituation muß man aber praktisch bewußt provozieren, was in einem korrekten Programm natürlich nie auftreten wird.
Im Zuge der Weiterentwicklung [...] (Objektorientierung) wird [...] in Zukunft umgestellt werden, so daß diese Inkonsistenzen nicht mehr auftreten können.
Noch hat die Zukunft offenbar nicht begonnen. Und wäre das toll, wenn alle Entwickler ab sofort nur noch korrekte Programme schreiben bzw. alle Fehler vor den Transport ins Produktivsystem gefunden werden.
(In einem der sapfans-Foren hat mal jemand die Einführung einer neuen ABAP-Anweisung vorgeschlagen: SET BUGS OFF. :)

Soll ich wirklich noch einen Versuch machen, den Fehler zu melden?
Das Problem, daß Änderungen an CONSTANTS sich auf die gepufferte Programm-Load auswirken, läßt sich nämlich zur Kompromittierung beliebiger anderer Rahmenprogramme mißbrauchen, die meinen Funktionsbaustein NIE (weder direkt noch indirekt) aufrufen.

Ein Beispiel:
Programm ZTEST2 hat den Quelltext:

Code: Alles auswählen.

REPORT  ztest2.
CONSTANTS: con_x VALUE 'X', con_y VALUE 'Y'.
CONSTANTS: con_d TYPE d VALUE IS INITIAL.

IF con_d EQ sy-datum.
  MESSAGE x000(38) WITH con_x con_y con_d sy-datum.
ENDIF.
IF con_x EQ con_y.
  MESSAGE x000(38) WITH con_x con_y con_d sy-datum.
ENDIF.

WRITE: / con_x, con_y, con_d, sy-datum.
Normalerweise sollten die Bedingungen in den IF-Anweisungen nicht wahr sein.
Also gibt das Programm in der Liste einfach die 4 Inhalte aus.

Jetzt kann ich als User 1 ein Programm ZTEST1 starten, das die Inhalte beliebiger Konstanten in der gepufferten Programm-Load von ZTEST2 beliebig ändert.
User 2, der eben noch mit Programm ZTEST2 eine Liste erzeugte, bekommt 2 Minuten später mit dem gleichen Programm einen Dump MESSAGE_TYPE_X:
Technische Informationen zur Nachricht:
Nachrichtenklasse... "38 "
Nummer.............. 000
Variable 1.......... "z "
Variable 2.......... "z "
Variable 3.......... "17.03.2002 "
Variable 4.......... "13.01.2003 "

Wenn User 2 den Fehler später reproduzieren will, klappt das nicht mehr, z.B. weil User 1 die Werte zurückgeändert hat.

Nicht reproduzierbares Fehlverhalten würde ich das dann nennen.
(Jedenfalls aus Sicht von User 2 und aus Sicht des Admins/Entwicklers, der mit der Fehlersuche betraut ist.
Und eine OSS-Fehlermeldung mit so einer wirren Problembeschreibung dürfte auch wenig Aussicht auf Erfolg haben.
Wer weiß schon, ob der Fehler bei einer Remote-Einwahl des SAP-Supports reproduzierbar ist oder nicht. Vermutlich nur User 1.)

Inzwischen glaube ich auch fast, es wäre besser ich veröffentliche nicht, WIE man das machen könnte.

Andererseits ist dieser Bug schon älter als vier Jahre, vermutlich schon so alt wie R/3, d.h. älter als 10 Jahre. Man darf also davon ausgehen, daß er nicht nur mir bekannt ist.

Und gegen böswillige Entwickler helfen sowieso nur unabhängige Code-Reviews, möglichst automatisierte Scans nach "gefährlichen" Anweisungen, wie z.B. GENERATE SUBROUTINE POOL, INSERT REPORT, ...
Beliebige Hintertüren lassen sich problemlos in Quelltexten verstecken, wenn nicht überprüft wird, was ins Produktivsystem transportiert werdern soll.
Wer diese Möglichkeiten hat, macht sich kaum die Mühe, per Manipulation an Konstanten ... sein Ziel zu erreichen. Da gibt es viel direktere Wege.

Je nach Sicherheitsbedürfnis bzw. Paranoia kann man dann zum Wettrüsten gegen die Anwender / Entwickler übergehen, mit Security Audit Log, Definition von Triggern auf DB-Ebene, ... Tripwire-ähnlichen Konsistenzprüfungen für alle Objekte der Development Workbench, ...

Klar könnte man auch für das Ausnutzen dieses Fehlers ein Scan-Programm schreiben, das verdächtige Funktionen findet.
Aber wäre es nicht besser, SAP würde das Problem lösen?

Ich habe bisher auch noch keinen Kunden gesehen, der sich auch nur annähernd der Risiken bewußt wäre bzw. wirklich etwas davon wissen will.
Kennst Du einen? Vielleicht hat ja jemand Interesse an Prüfprogrammen, die nach mir bekannten und von SAP nicht gefixten Exploits suchen.
Bzw. nach Exploits, die gar nicht von SAP gefixt werden können, z.B. weil schon immer einfach alles ins Produktivsystem transportiert wurde.)
Obwohl: In den Systemen, in denen entsprechende Scan-programme laufen, könnte auch wieder jeder Entwickler sich den Quelltext ansehen, prüfen wonach ich suche, und evtl. auf dumme Ideen kommen. Das will man ja wirklich nicht.

Das öffentliche Posten von Exploits ist sicher kein wünschenswertes Vorgehen.
Obwohl es auch so etwas gibt.
Das sollte man als Anwender schon wissen. Ein engagierter Angreifer findet diese Hinweise bestimmt. (Wenn er überhaupt erst danach suchen muß und nicht schon genügend Wege kennt, sein Ziel zu erreichen.)
Ein Beispiel unter vielen: http://www.lan-ks.de/~jochen/sap-r3/ora-hack.html
Dazu noch ein Tool wie ettercap, dann kann man sich ausmalen, was von der Sicherheit der SAP-Systeme übrig bleibt.

Gängige Security Audits helfen da wenig.

Aber auf Probleme hinzuweisen ist ja nicht unbedingt identisch mit dem Veröffentlichen von Anweisungen, wie sich ein Fehler ausnutzen läßt.
Daher möchte man schon manchmal widersprechen, wenn man hört:

"Das Cracken von SAP-Paßwörtern ist nicht möglich."
Daher hat SAP vermutlich auch kein Problem darin gesehen, für den User TMSADM ein Trivialpaßwort zu vergeben. Aber wissen die Kunden, daß sie tunlichst vermeiden sollten, die Berechtigungen dieses Users zu erweitern?

Und wenn SAP-Paßwörter nicht geknackt werden können, warum sollte dann jemand den Nutzern vorschreiben, wie lang ihr Paßwort sein muß, wie oft es zu ändern ist, ...

Und nur mal so als Beweis, daß sich Paßwörter DOCH knacken lassen (ohne gleich die Sicherheit wichtiger Systeme zu kompromittieren):

Die Paßwörter der User BCUSER und DDIC des MiniWAS-Systems für Windows, das dem Buch ABAP Objects beiliegt, stehen ja im Anmeldebild.
Nicht im Anmeldebild stehen die Paßwörter der User CFOLDERS, JBROWN und LJONES, die alle drei das gleiche 4buchstabige Trivialpaßwort haben, sowie die ehemaligen Paßworte des Users DDIC (auch die alten Hash-Werte stehen in der USR02): CLAUDIA, JOERG, EXPORT, ROMERO, 19920706, die mit den alten Paßwörtern des Users DDIC im WAS TestDrive for Linux Release 6.10 (SAP DB) übereinstimmen.).
Im WAS TestDrive for Linux Release 5.0A (die CeBIT-Version) hat der User DDIC als ehemalige Paßworte z.B. ACONITUM, 50BASIS, BASIS und INIT gehabt. ... usw.

Mit einem halbwegs aktuellen PC (CPU: AMD Athlon(tm) XP 2100+ stepping 02) und getuntem Crack-Programm kann man ca. 7.5 Millionen Kombinationen pro Minute prüfen.
Und es ist unmöglich, alle Wortlisten in die USR40 einzutragen.
Allenfalls Firmennamen, Produktnamen, SY-SYSID ... kann man da noch eintragen.

Welcher Durchschnittsanwender denkt schon, daß selbst bei Verwendung eines normalen PCs jedes 5stellige Paßwort innerhalb von Stunden, jedes 6stellige innerhalb von Tagen geknacht ist ...
Und daß die Paßworte GEHEIM12, YXCVBNM, TROWSSAP vermutlich innerhalb der ersten Minuten geknackt sind?

Und selbst wenn alle Anwender geschult sind, wie man sichere Paßwörter wählt, die man sich dennoch merken kann, so daß sie das Paßwort nicht per Zettel am Bildschirm oder unter der Tastatur "speichern", muß man immer noch darauf vertrauen, daß der Hash-Algorithmus nicht defekt ist (Design oder Implementierung betreffend) und nicht z.B. für ein vermeintlich äußerst kompliziertes Paßwort den gleichen Hashwert zurückliefert wie für ein Trivialpaßwort.

Dieses Vertrauen ist leider NICHT gerechtfertigt. SAPs Aussagen zum Paßwort-Algorithmus sind ein schlechter Witz.
MD5-ähnlich? eine MD5-Prüfsumme ist doch länger als die 8 Bytes für den Hashwert.
Und ab wann übersteigen die möglichen Kollisionen (identischer Hashwert trotz verschiedener Paßworte) ein erträgliches Maß? Wenn man hundert verschiedene Paßwörter findet, die alle den gleichen Hashwert haben? Oder erst ab 1000? Oder ab einer Million?
Ich kann das: Ein Paßwort für den User DITTRICH finden, zu dem sich eine Millionen oder mehr verschiedene Paßwörter finden lassen, die alle den gleichen Hashwert haben.
Und noch mal Millionen anderer Paßworte, die alle einen weiteren identischen Hashwert haben ....
Eine weitere Million Paßwörter mit identischem Hashwert für den User DDIC, SAP* oder EARLYWATCH. ...

Muß jetzt jeder SAP-Anwender zu Hause ein Crack-Programm starten, damit er nicht Gefahr läuft, daß jemand anders mit Kenntnis der Hashwerte ein Trivialpaßwort findet, so daß er sich unter fremdem Account illegalen Zugang verschafft?
Oder hätte bei SAP mal jemand auf den Quelltext schauen sollen, als der alte Hash-Algorithmus (CODVN = 'A' mit noch mehr Schwächen) durch den neuen (mit CODVN = 'B') ersetzt wurde.

Und auch ein weiteres Problem ist nicht wirklich bereinigt worden. Mit CODVN = 'A' hatten 2 User, deren Name in den ersten 6 Zeichen übereinstimmt, immer den gleichen Hashwert für das gleiche Paßwort.
Das ist jetzt (CODVN = 'B') nicht mehr der Fall. Die Wahrscheinlichkeit entsprechender Kollisionen hat sich gegebüber CODVN = 'A' sicher verbessert.
Dennoch gibt es unterschiedliche gültige Namen für SAP-Benutzer, die (für jemanden, der SAP-Paßworte cracken will, vorhersagbar) zum gleichen SALT führen. Ein Angreifer muß also nicht mal für jede Kombination User/Paßwort den Hashwert ermitteln.
Auch das ist ein Fehler in der Implementierung.

Da ich nicht mal systematisch nach Schwächen im Hash-Algorithmus gesucht habe, sondern eher zufällig darauf gestoßen bin, weil ich die (lückenhafte) SAP-Dokumentation verstehen wollte und ein kleines Testprogramm geschrieben habe, kann ich mir nicht vorstellen, daß SAP nichts von den Schwächen weiß. (Deswegen halten sich doch wohl SAP-Mitarbeiter bei Fragen zum aktuell verwendeten Algorithmus immer ziemlich bedeckt.)

Es nützt also wenig, daß SAP versucht, das Cracken zu erschweren. Die Hürden lassen sich bei entsprechendem Wissen umgehen.
(Wenn man schon den Weg gehen will, das Cracken zu erschweren, hätte SAP noch wesentlich mehr tun können, aber nicht getan. Mein eMail-Verkehr mit security [AT] sap dot com diesbezüglich hat mich nicht gerade beruhigt.)

Auf halbwegs gesicherten Unix-Systemen sind die Hashwerte von Paßwörtern schon lange nicht mehr in der öffentlich lesbaren Datei /etc/passwd gespeichert.
Aber zumindest in Entwicklungssystemen und Testsystemen kommt an die USR02 jeder heran.
(Daß man die USR02 dem Zugriff des normalen ABAP-Entwicklers besser entzogen hätte, wußten einige Entwickler bei SAP schon mind. seit 1993, jedenfalls kann ich mich an entsprechende Diskussionen erinnern.)
Vielleicht lassen sich ja aus dem geknackten aktuellen Paßwort (und den vorhergehenden) im Testsystem brauchbare Vorhersagen für das vermutliche Paßwort im Produktivsystem treffen.
(Und wenn nicht, macht nichts. Versucht man es halt morgen wieder. Der Anwender sieht ja nichts von einer einzelnen fehlgeschlagenen Anmeldung seit dem letzten Login.
Und der Admin sieht es evtl., kann aber nur bedingt wissen, ob Anwender A sich vertippt hat oder ob jemand anders unberechtigten Zugang zum System wollte.)

Auch im Produktivsystem kommt man je nach Berechtigung an den kompletten Tabelleninhalt von Tabellen (also auch der USR02 - zumindest für SY-MANDT), man braucht dazu aber weder die Berechtigung für Transaktion SE16 noch eine Berechtigung zum Objekt S_TABU_DIS.

Und weil die USR02 nun mal die Tabelle ist, mit der per SELECT SINGLE * FROM ... in fast jeder Transaktion die Gültigkeit eines Users geprüft wird, kann man noch nicht mal exzessiv alles loggen, was nicht mit BNAME = SY-UNAME auf die USR02 zugreift.
Eigentlich schade, Chance vertan. Aber es gibt ja eine neue: http://groups.google.de/groups?&selm=3D ... %40sap.com


"Ohne Developer Key kann man keine Objekte der Development Workbench ändern, ohne Object Key kein SAP-Standard-Objekt."
Auch darauf würde ich mich lieber nicht verlassen.

"SAPMSYST ist sicher vor Manipulationen, weil der Quelltext geschützt ist."
Also muß man nicht prüfen (können), ob jemand das Programm manipuliert hat ....
Wer's glaubt. Ich weiß es allerdings besser.

----------
Ich höre lieber erst mal auf. Es ist schon erschreckend, was man alles findet, wenn man ein wenig sucht.
I thought you had enough troubleshooting to do, cleaning up after the 'low cost' alternatives...
Ja, ich habe schon oft ausbaden müssen, daß nicht zu Anfang jemand gefragt wurde, der sich auskennt.
Nicht immer war "low cost" der Grund.
Auch ziemlich teure Berater/Beratungsunternehmen sind nicht unbedingt Garant für Qualität. Und man kann sich scheinbar auch 10 Jahre lang durchschlagen, ohne viel dazuzulernen.

Schlimm wird es, wenn es bei $Kunde niemanden gibt, der die Qualifikation der Leute beurteilen kann. Dann ist man auf Gedeih und Verderb der Leistung des Anbieters ausgeliefert.
Miese Qualität wird noch dadurch belohnt, daß man zu jedem Releasewechsel wieder viel Beratungsleistung verkaufen kann, um das wieder hinzubiegen, was man schon vorher hätte besser machen können/sollen. ...
Und alle denken, das muß so sein und glauben den Beratern, wenn sie sagen: "Das geht mit SAP nicht anders..."
Have you seen the latest questions on sapfans? It gets worse every day.
Ja, grausam.
Für die, die nicht ab und zu mal bei sapfans.com reinschauen, ein paar Musterbeispiele:
http://www.sapfans.com/forums/viewtopic.php?t=14582
(Ich schwöre, daß keines dieser Postings von mir ist, obwohl - manchmal wäre es schon verlockend, auf so einen Thread mit EXEC SQL. TRUNCATE TABLE ... zu antworten.)
http://www.sapfans.com/forums/viewtopic.php?t=7283
(Erst mal die falsche Überschrift zum Problem, dann nicht merken, daß die Frage beantwortet wurde, ... Da macht das Antworten dann wirklich keinen Spaß mehr. (Abgesehen davon, für die meisten Fragen wäre Auto-Response "Use the F1 key!" die passende Antwort.))

Wollen die alle erst noch mit SAP Geld verdienen? Oder sind sie schon auf richtige Systeme losgelassen worden?
Mindestens zwei von drei Fragen sind mit der F1-Taste zu beantworten.
Stattdessen kommen 17 Alternativen "You could try this"/ "I always did it this way, but ..."
Und wenn man einige der Fragen sieht, bekommt man eine dunkle Vorahnung davon, welche Fragen gar nicht erst gestellt werden. Und was man vermutlich in produktiven Systemen so alles vorfinden wird.
Aber wozu denn die Leute qualifizieren bzw. qualifizierte Leute engagieren?
Wenn sie etwas nicht wissen, können sie ja auf sapfans.com fragen. Und wenn sie dort keine Antwort kriegen, kann man ja noch den SAP Support damit behelligen.
(Oft genug wird das sogar funktionieren, weil scheinbar der First Level Support pro beantworteter Frage bezahlt wird. Egal ob es überhaupt eine Frage war, die eine OSS-Fehlermeldung rechtfertigt.)

Andererseits wird mit Eifer daran gearbeitet,
-daß die erweiterte Syntaxprüfung keine Warnung mehr hochbringt (Du erinnerst Dich bestimmt -statt TRANSLATE ... TO LOWER CASE einen SAP-Standard-FB aufrufen, der das TRANSLATE macht),
-Lösungen zu finden, wie man eine Rechtschreibkorrektur in den Kommentarzeilen des Quelltextes hinkriegt, ...
-...
Es scheint nicht besonders verbreitet zu sein, Anforderungen auch mal zu hinterfragen bzw. überhaupt zu versuchen, den Sinn von Anforderungen zu verstehen.
Oh, and Happy New Year to you!
Gleichfalls.
Das erinnert mich wieder daran, daß ich mich endlich mal um ein neues Projekt kümmern müßte.
Eigentlich wollte ich nach meinem letzten Projekt (Namensraum-Umstellung) schon etwas länger Pause machen. Aber wenn ich nicht bald wieder arbeite, könnte ich mich glatt an den jetzigen Zustand gewöhnen.
Erholt bin ich jetzt, auch der Kanada-Urlaub ist schon eine Weile her. Mal sehen, wie schwer es wird, was Neues zu finden.

Ich habe vor einiger Zeit Dein Posting im Jobs-Forum von sapfans.com gesehen. Hast Du inzwischen etwas gefunden?

Frank

Beitrag von Wolfgang G. Propfe (ForumUser / 23 / 0 / 0 ) »
Moin Frank:
I enjoyed your posting - or shall I call it an article? You should write a book on SAP users, external consultants and in-house developers. Then every hiring manager and recruiter should be forced to read it.

My recent posting in the jobs forum was about highlighting the way recruiters are more shameless by the hour.
http://www.sapfans.com/forums/viewtopic.php?t=11345
- First Cindy needs someone for $70/h and posts the requirements.
- The next day, she has enough people.
- The day after that, she needs one more (same job), but she adds to the requirements and lowers the rate to $60-65/h.

I am not looking for a job right now, but I may be later this year around May or August. I may skip Cindy though. :-)

Once (with the consent of the Basis Admin) I executed another kind of attack on the USR02. Rather than cracking the password, I overwrote it. The Admin did not realize I used HIS account until I told and showed him. I fully agree, that weaknesses should be fixed and not ruled 'out-of-scope' or 'by design', which is definitely true for your 'changed constant' FM. Symmetric password encryptions are always tricky in an open source code system with a DB attached. If you can debug any program, view the variable contents and decrypt the password/key, you can use this information to do other things. Handling keys is probably one of the very rare cases, where hiding a program (or include) makes sense.

On the job, I usually go by what SAP does and how. In other words, you don't have to be better than the rest of the (standard) system. While you could, you would be driving up the cost faster and higher than the benefits. By the same reasoning, I don't want to be the weakest link either, so my design and programming style is in line with SAP's. This also explains, why I am not so active on sapfans anymore.

Give it a few more years, and we can post all the secrets and flaws in plain text. By then the majority of the new SAP consultants won't understand it anyway...

Take care.

Regards,
Wolfgang

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
Wolfgang G. Propfe hat geschrieben:You should write a book on SAP users, external consultants and in-house developers. Then every hiring manager and recruiter should be forced to read it.
If you promise to force "every hiring manager and recruiter" to read the book, I promise to write it.
Once (with the consent of the Basis Admin) I executed another kind of attack on the USR02. Rather than cracking the password, I overwrote it. The Admin did not realize I used HIS account until I told and showed him.
Daß es auch noch eine "Holzhammer"-Methode gibt, war mir schon klar. Ich habe sie absichtlich nicht erwähnt, weil ich fürchte, daß es genügend SAP-Installationen gibt, bei denen man entsprechende Mittel bis ins Produktivsystem bekommt, ohne daß es jemandem auffällt.
Symmetric password encryptions are always tricky in an open source code system with a DB attached.
Bisher habe ich keinen Grund daran zu zweifeln, daß ein One-way Hash-Algorithmus verwendet wird, der also nicht umkehrbar ist.

Hat zwar mit Strings und C-Feldern nicht mehr viel zu tun...

Beitrag von Frank Dittrich (Expert / 674 / 0 / 15 ) »
Auch wenn der Thread mit Strings vs. C-Felder schon nichts mehr zu tun hat.
Wolfgang hat geschrieben:Symmetric password encryptions are always tricky in an open source code system with a DB attached.
Bisher habe ich keinen Grund daran zu zweifeln, daß ein One-way Hash-Algorithmus verwendet wird, der also nicht umkehrbar ist.
Jochen Hein hat ja schon vor einiger Zeit auf seiner Website die Vermutung geäußert, daß der SAP-Paßwort-Verschlüsselungs-Algorithmus umkehrbar ist, aber ich wollte es irgendwie nicht wahrhaben.
Heute habe ich aber den Beweis gefunden, daß der Verschlüsselungsalgorithmus umkehrbar sein muß.
MD5? Die von SAP behauptete Ähnlichkeit mit diesem Hash-Algorithmus düfte wohl immer geringer werden.
Ein Kryptographie-Experte (ich bin keiner) sollte also auch ohne SAP-Kernel-Sourcen keine Schwierigkeiten haben, aus den Hashwerten auf die Paßworte zu kommen, indem er eine genügend große Menge Vergleichsdaten auswertet.

Doch noch eine Überarbeitung/Richtigstellung, am Ende eines anstrengenden Tages sollte man vielleicht doch nicht so voreilig Schlüsse ziehen. Mir ist gerade klar geworden, daß es auch eine ganz andere, nicht weniger einleuchtende Erklärung für das Phänomen gibt, aus dem ich so schnell den Schluß auf einen umkehrbaren "Verschlüsselungs"-Algorithmus gezogen habe.
(Da dies noch das letzte Posting im Thread ist, hätte ich vielleicht hoffen können, es hat noch niemand gelesen, und schnell den Beitrag wieder löschen sollen. 8) )

Seite 1 von 1

Vergleichbare Themen

3
Antw.
3276
Views
Ikonen-Strings
von jondahl11 » 15.09.2006 11:23 • Verfasst in ABAP® für Anfänger
1
Antw.
2051
Views
Arbeiten mit INT und STRINGS
von flexed » 11.08.2006 17:45 • Verfasst in ABAP® Core
1
Antw.
4806
Views
Strings vergleichen
von meocon » 04.09.2008 16:37 • Verfasst in ABAP® für Anfänger
1
Antw.
1806
Views
Strings verursachen Zeilenumbruch
von Dahmser » 28.08.2009 14:29 • Verfasst in ABAP® für Anfänger
0
Antw.
1084
Views
Zerlegen eines Strings
von Happy24 » 04.07.2006 15:08 • Verfasst in ABAP® Core

Aktuelle Forenbeiträge

IF mit AND und OR
vor 5 Tagen von DeathAndPain 10 / 31821
BAPI zur ABSO?
vor 4 Wochen von DeathAndPain 2 / 14745

Newsletter Anmeldung

Keine Beiträge verpassen! Wöchentlich versenden wir lesenwerte Beiträge aus unserer Community.
Die letzte Ausgabe findest du hier.
Details zum Versandverfahren und zu Ihren Widerrufsmöglichkeiten findest du in unserer Datenschutzerklärung.