Rennes did most of the pressing in the first half and was rewarded five minutes before the interval when M'Baye Niang poked home his ninth goal of the campaign in all competitions.
Bruno Genesio's men pegged it back shortly after the restart through Bertrand Traore's unerring finish although captain Benjamin Andre restored the lead with a 55th-minute header.
Moussa Dembele drew the host level again from the penalty spot after 75 minutes but Ramy Bensebaini had the final say, lashing in from 18 yards nine minutes from time to book Rennes's place at the Stade de France later this month.
After a quiet opening half-hour, Nabil Fekir burst into life and twice forced Tomas Koubek into smart saves in quick succession.
The importance of those stops was highlighted shortly before the interval when Niang stole in front of Marcelo to prod home Ismaila Sarr's teasing cross from the right.
That lead was wiped out two minutes after the restart, though, as Traore collected a loose ball inside the area, took a touch and fired low into Koubek's bottom-right corner.
Anthony Lopes superbly pawed away Hatem Ben Arfa's fizzing drive soon after but there was little he could do from the resulting corner as Andre climbed highest to head home from eight yards.
Andre was involved in Lyon's leveller 15 minutes from time as referee Benoit Bastien adjudged him to have handled Houssem Aouar's shot inside the area. Dembele made no mistake from the resulting spot-kick, tucking low to Koubek's left.
With extra time looming, Bensebaini thundered home with his right foot from the edge of the area to settle an action-packed semi-final.